Merge origin/master into codex/investigate-native-sandbox-interruption

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-11-03 17:37:41 -06:00
commit 382318aa66
345 files changed
+2906 -1635

No files matched your search

+14 -4
View File
@@ -762,17 +762,27 @@ path enforces a project maximum.
and a partial unique index allows one unsettled attempt per run. The row keeps
the context revision, the nullable primary project as provenance (no foreign
key) and the settlement identity, which is set together with `settled_at`.
`admitted_project_ids` is the complete admitted project set, sorted, kept
after settlement and immutable (a trigger rejects any change). It has no
project foreign key, so settled history never blocks project deletion and
keeps the deleted project's ID. `{}` is a known empty set. Null means
unknown and is allowed only on settled rows: migration `0428` filled it from
the charges of unsettled rows, but settled `0426` rows had already lost
their set. A retained provider session resumes with the previous set ∪ the
current references; an unknown previous set requires a fresh context, never
an empty or primary-only guess.
- `execution_admission_project_charges` stores one row per distinct admitted
project, including unlimited projects. An admission without projects has no
rows. Composite `(company_id, …)` foreign keys keep admissions and projects in
one company. Rows exist only while the admission is unsettled: settlement
deletes the whole set in one transaction, and `(company_id, project_id)` is
indexed for active counts, so lookups scale with active work, not history.
one company. Rows are the only active capacity and exist only while the
admission is unsettled: settlement deletes the whole set in one transaction,
and `(company_id, project_id)` is indexed for active counts, so lookups
scale with active work, not history.
Archiving a project changes no charge. Deleting a project with a charge returns
`409 project_execution_admission_pending`; the foreign key is the backstop.
Deleting an agent refuses unsettled admissions on its runs and removes settled
ones. Company deletion removes the company's admissions before its runs and
ones, with their sets, together with the runs. Company deletion removes the company's admissions before its runs and
projects.
The proposed global lock order for the admission transaction is: company row
+8 -1
View File
@@ -1186,7 +1186,8 @@ process per layer. Each layer reads the earlier layers as declarations:
| `below` | Files the largest import cycle depends on, and unrelated files |
| `cycle` | The largest import cycle |
| `above` | Files that depend on the cycle |
| `tests` | Test files |
| `tests` | Test files outside `src/__tests__` |
| `test-tree-0` to `test-tree-2` | `src/__tests__`, in shards |
The generated configs and declarations go in
`server/node_modules/.cache/paperclip-typecheck`. You do not maintain a layer list.
@@ -1195,6 +1196,12 @@ The server typecheck builds only the Runner's TypeScript output. Use
`pnpm --filter @paperclipai/paperclip-runner build` when a local check needs the
Runner binary.
`src/__tests__/tsconfig.json` holds the options for the test-tree layers, so an
editor and the typecheck report the same errors. Server tests reach Daytona plugin
source, but the root install does not install that plugin's dependencies. So this
config maps `@daytonaio/sdk` to `server/scripts/typecheck-stubs/daytona-sdk.d.ts`.
The plugin's own project checks that source against the real package.
If the memory available to the task is limited, use this command:
```sh
+4
View File
@@ -24,6 +24,10 @@ Creation, installation, message waiting, and success use the same production
components and server evidence as the own-app flow. A click or OAuth return does
not constitute successful configuration.
Reopening a completed Slack setup retains its Slack handoff. **Done** opens the
saved connection settings without repeating activation or reactivating a paused
bot. Unfinished setups still complete their normal server verification first.
## Cloud deployment prerequisites
Managed setup is disabled by default. Qualification requires all of:
@@ -5,9 +5,11 @@ import { describe, expect, it } from "vitest";
import { applyPendingMigrations, inspectMigrations } from "./client.js";
import { EMBEDDED_POSTGRES_TEST_TIMEOUT_MS, getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./test-embedded-postgres.js";
const hashMigration = async (name: string) => createHash("sha256")
.update(await readFile(new URL(`./migrations/${name}`, import.meta.url), "utf8")).digest("hex");
const migrationName = "0426_absent_guardian.sql";
const migrationHash = createHash("sha256")
.update(await readFile(new URL(`./migrations/${migrationName}`, import.meta.url), "utf8")).digest("hex");
const migrationHash = await hashMigration(migrationName);
const retainedSetMigration = "0428_retained_admitted_project_sets.sql";
const support = await getEmbeddedPostgresTestSupport();
const describePostgres = support.supported ? describe : describe.skip;
@@ -17,7 +19,8 @@ describePostgres("execution admission migration", () => {
const sql = postgres(database.connectionString, { max: 1, onnotice: () => {} });
try {
await sql`DROP TABLE execution_admission_project_charges, execution_admissions, project_task_execution_policies`;
await sql`DELETE FROM drizzle.__drizzle_migrations WHERE hash = ${migrationHash}`;
await sql`DROP FUNCTION paperclip_execution_admission_project_set_immutable`;
await sql`DELETE FROM drizzle.__drizzle_migrations WHERE hash IN (${migrationHash}, ${await hashMigration(retainedSetMigration)})`;
const companyId = randomUUID(), agentId = randomUUID(), projectId = randomUUID();
await sql`INSERT INTO companies (id, name, issue_prefix) VALUES (${companyId}, 'Upgrade', 'UPG')`;
await sql`INSERT INTO agents (id, company_id, name, adapter_type) VALUES (${agentId}, ${companyId}, 'Agent', 'process')`;
@@ -31,7 +34,7 @@ describePostgres("execution admission migration", () => {
});
const before = await contents();
expect(await inspectMigrations(database.connectionString))
.toMatchObject({ status: "needsMigrations", pendingMigrations: [migrationName] });
.toMatchObject({ status: "needsMigrations", pendingMigrations: [migrationName, retainedSetMigration] });
await applyPendingMigrations(database.connectionString);
@@ -50,4 +53,49 @@ describePostgres("execution admission migration", () => {
await database.cleanup();
}
}, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS);
it("keeps the admitted sets of shipped 0426 rows that still have charges and marks settled ones unknown", async () => {
const database = await startEmbeddedPostgresTestDatabase("paperclip-execution-admission-sets-");
const sql = postgres(database.connectionString, { max: 1, onnotice: () => {} });
try {
await sql`DROP TRIGGER paperclip_execution_admission_project_set_immutable ON execution_admissions`;
await sql`DROP FUNCTION paperclip_execution_admission_project_set_immutable`;
await sql`ALTER TABLE execution_admissions DROP CONSTRAINT execution_admissions_project_set_check, DROP COLUMN admitted_project_ids`;
await sql`DELETE FROM drizzle.__drizzle_migrations WHERE hash = ${await hashMigration(retainedSetMigration)}`;
const companyId = randomUUID(), agentId = randomUUID();
const [a, b] = [randomUUID(), randomUUID()].sort();
await sql`INSERT INTO companies (id, name, issue_prefix) VALUES (${companyId}, 'Upgrade', 'UPS')`;
await sql`INSERT INTO agents (id, company_id, name, adapter_type) VALUES (${agentId}, ${companyId}, 'Agent', 'process')`;
await sql`INSERT INTO projects (id, company_id, name) VALUES (${b}, ${companyId}, 'B'), (${a}, ${companyId}, 'A')`;
const [live, empty, settled] = [randomUUID(), randomUUID(), randomUUID()];
for (const id of [live, empty, settled]) {
const [run] = await sql`INSERT INTO heartbeat_runs (company_id, agent_id) VALUES (${companyId}, ${agentId}) RETURNING id`;
await sql`INSERT INTO execution_admissions (id, company_id, run_id, attempt, primary_project_id, context_revision,
settlement_id, settled_at) VALUES (${id}, ${companyId}, ${run.id}, 1, ${a}, 'issue:1',
${id === settled ? randomUUID() : null}, ${id === settled ? sql`now()` : null})`;
}
await sql`INSERT INTO execution_admission_project_charges (admission_id, company_id, project_id)
VALUES (${live}, ${companyId}, ${b}), (${live}, ${companyId}, ${a})`;
const sets = () => sql`SELECT id, admitted_project_ids FROM execution_admissions ORDER BY id`;
await applyPendingMigrations(database.connectionString);
const upgraded = await sets();
expect(upgraded).toEqual(expect.arrayContaining([
{ id: live, admitted_project_ids: [a, b] },
{ id: empty, admitted_project_ids: [] },
{ id: settled, admitted_project_ids: null },
]));
await sql`DELETE FROM drizzle.__drizzle_migrations WHERE hash = ${await hashMigration(retainedSetMigration)}`;
await applyPendingMigrations(database.connectionString);
expect(await sets()).toEqual(upgraded);
expect(await sql`SELECT count(*)::int AS count FROM execution_admission_project_charges`).toEqual([{ count: 2 }]);
await expect(sql`UPDATE execution_admissions SET admitted_project_ids = '{}' WHERE id = ${settled}`)
.rejects.toMatchObject({ message: "execution admission project set is immutable" });
expect(await inspectMigrations(database.connectionString)).toMatchObject({ status: "upToDate" });
} finally {
await sql.end();
await database.cleanup();
}
}, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS);
});
@@ -0,0 +1,22 @@
ALTER TABLE "execution_admissions" ADD COLUMN IF NOT EXISTS "admitted_project_ids" uuid[];--> statement-breakpoint
-- Unsettled charges are still the complete admitted set. Settled rows already lost theirs and stay unknown (null).
UPDATE "execution_admissions" AS "admission" SET "admitted_project_ids" = coalesce((
SELECT array_agg("charge"."project_id" ORDER BY "charge"."project_id") FROM "execution_admission_project_charges" AS "charge"
WHERE "charge"."company_id" = "admission"."company_id" AND "charge"."admission_id" = "admission"."id"
), '{}'::uuid[]) WHERE "admission"."settled_at" IS NULL AND "admission"."admitted_project_ids" IS NULL;--> statement-breakpoint
ALTER TABLE "execution_admissions" DROP CONSTRAINT IF EXISTS "execution_admissions_project_set_check";--> statement-breakpoint
ALTER TABLE "execution_admissions" ADD CONSTRAINT "execution_admissions_project_set_check" CHECK (case when "execution_admissions"."admitted_project_ids" is null
then "execution_admissions"."settled_at" is not null else array_position("execution_admissions"."admitted_project_ids", null) is null and cardinality("execution_admissions"."admitted_project_ids") <= 100 end);--> statement-breakpoint
CREATE OR REPLACE FUNCTION paperclip_execution_admission_project_set_immutable() RETURNS trigger
LANGUAGE plpgsql SET search_path = public, pg_temp AS $$
BEGIN
IF NEW.admitted_project_ids IS DISTINCT FROM OLD.admitted_project_ids THEN
RAISE EXCEPTION 'execution admission project set is immutable' USING ERRCODE = 'check_violation';
END IF;
RETURN NEW;
END;
$$;
--> statement-breakpoint
DROP TRIGGER IF EXISTS paperclip_execution_admission_project_set_immutable ON "execution_admissions";--> statement-breakpoint
CREATE TRIGGER paperclip_execution_admission_project_set_immutable BEFORE UPDATE OF admitted_project_ids ON "execution_admissions"
FOR EACH ROW EXECUTE FUNCTION paperclip_execution_admission_project_set_immutable();
@@ -1,6 +1,6 @@
{
"id": "fe0b877c-d63e-46d9-8798-c7729fd13eb7",
"prevId": "deb3f1b9-6eb7-447f-adf2-dee69f943c59",
"id": "fb4383f6-cbe6-4aff-9fd2-6eeb69a6927a",
"prevId": "dd860bff-55de-4849-8787-58b9de729258",
"version": "7",
"dialect": "postgresql",
"tables": {
@@ -28825,6 +28825,337 @@
"checkConstraints": {},
"isRLSEnabled": false
},
"public.execution_admission_project_charges": {
"name": "execution_admission_project_charges",
"schema": "",
"columns": {
"admission_id": {
"name": "admission_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"company_id": {
"name": "company_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"project_id": {
"name": "project_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"execution_admission_project_charges_project_idx": {
"name": "execution_admission_project_charges_project_idx",
"columns": [
{
"expression": "company_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "project_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"execution_admission_project_charges_company_admission_fk": {
"name": "execution_admission_project_charges_company_admission_fk",
"tableFrom": "execution_admission_project_charges",
"tableTo": "execution_admissions",
"columnsFrom": [
"company_id",
"admission_id"
],
"columnsTo": [
"company_id",
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"execution_admission_project_charges_company_project_fk": {
"name": "execution_admission_project_charges_company_project_fk",
"tableFrom": "execution_admission_project_charges",
"tableTo": "projects",
"columnsFrom": [
"company_id",
"project_id"
],
"columnsTo": [
"company_id",
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"execution_admission_project_charges_admission_id_project_id_pk": {
"name": "execution_admission_project_charges_admission_id_project_id_pk",
"columns": [
"admission_id",
"project_id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.execution_admissions": {
"name": "execution_admissions",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"company_id": {
"name": "company_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"run_id": {
"name": "run_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"attempt": {
"name": "attempt",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"primary_project_id": {
"name": "primary_project_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"context_revision": {
"name": "context_revision",
"type": "text",
"primaryKey": false,
"notNull": true
},
"admitted_project_ids": {
"name": "admitted_project_ids",
"type": "uuid[]",
"primaryKey": false,
"notNull": false
},
"settlement_id": {
"name": "settlement_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"settled_at": {
"name": "settled_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"execution_admissions_company_id_uq": {
"name": "execution_admissions_company_id_uq",
"columns": [
{
"expression": "company_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
},
"execution_admissions_run_attempt_uq": {
"name": "execution_admissions_run_attempt_uq",
"columns": [
{
"expression": "run_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "attempt",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
},
"execution_admissions_live_run_uq": {
"name": "execution_admissions_live_run_uq",
"columns": [
{
"expression": "run_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"where": "\"execution_admissions\".\"settled_at\" is null",
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"execution_admissions_company_id_companies_id_fk": {
"name": "execution_admissions_company_id_companies_id_fk",
"tableFrom": "execution_admissions",
"tableTo": "companies",
"columnsFrom": [
"company_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"execution_admissions_run_id_heartbeat_runs_id_fk": {
"name": "execution_admissions_run_id_heartbeat_runs_id_fk",
"tableFrom": "execution_admissions",
"tableTo": "heartbeat_runs",
"columnsFrom": [
"run_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {
"execution_admissions_attempt_check": {
"name": "execution_admissions_attempt_check",
"value": "\"execution_admissions\".\"attempt\" > 0"
},
"execution_admissions_settlement_check": {
"name": "execution_admissions_settlement_check",
"value": "(\"execution_admissions\".\"settlement_id\" is null) = (\"execution_admissions\".\"settled_at\" is null)"
},
"execution_admissions_project_set_check": {
"name": "execution_admissions_project_set_check",
"value": "case when \"execution_admissions\".\"admitted_project_ids\" is null\n then \"execution_admissions\".\"settled_at\" is not null else array_position(\"execution_admissions\".\"admitted_project_ids\", null) is null and cardinality(\"execution_admissions\".\"admitted_project_ids\") <= 100 end"
}
},
"isRLSEnabled": false
},
"public.project_task_execution_policies": {
"name": "project_task_execution_policies",
"schema": "",
"columns": {
"project_id": {
"name": "project_id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"company_id": {
"name": "company_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"max_concurrent_tasks": {
"name": "max_concurrent_tasks",
"type": "integer",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"project_task_execution_policies_company_project_fk": {
"name": "project_task_execution_policies_company_project_fk",
"tableFrom": "project_task_execution_policies",
"tableTo": "projects",
"columnsFrom": [
"company_id",
"project_id"
],
"columnsTo": [
"company_id",
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {
"project_task_execution_policies_maximum_check": {
"name": "project_task_execution_policies_maximum_check",
"value": "\"project_task_execution_policies\".\"max_concurrent_tasks\" is null or \"project_task_execution_policies\".\"max_concurrent_tasks\" > 0"
}
},
"isRLSEnabled": false
},
"public.execution_workspace_repositories": {
"name": "execution_workspace_repositories",
"schema": "",
@@ -29999,6 +30330,22 @@
"concurrently": false,
"method": "btree",
"with": {}
},
"execution_workspace_snapshots_imported_children_idx": {
"name": "execution_workspace_snapshots_imported_children_idx",
"columns": [
{
"expression": "(\"metadata\"->'importedWorkspaceChildren') jsonb_path_ops",
"asc": true,
"isExpression": true,
"nulls": "last"
}
],
"isUnique": false,
"where": "\"execution_workspace_snapshots\".\"metadata\" ? 'importedWorkspaceChildren'",
"concurrently": false,
"method": "gin",
"with": {}
}
},
"foreignKeys": {},
@@ -50480,6 +50827,12 @@
"primaryKey": false,
"notNull": false
},
"created_by_run_id": {
"name": "created_by_run_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"dispatch_fingerprint": {
"name": "dispatch_fingerprint",
"type": "text",
@@ -62442,6 +62795,12 @@
"primaryKey": false,
"notNull": false
},
"cleanup_verified_at": {
"name": "cleanup_verified_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
+7
View File
@@ -2983,6 +2983,13 @@
"when": 1793736361959,
"tag": "0427_ordinary_xavin",
"breakpoints": true
},
{
"idx": 428,
"version": "7",
"when": 1793743828770,
"tag": "0428_retained_admitted_project_sets",
"breakpoints": true
}
]
}
@@ -24,7 +24,10 @@ export const projectTaskExecutionPolicies = pgTable("project_task_execution_poli
/**
* One admission attempt of a run. A run can be admitted again after a settled
* attempt, so the run ID is not the admission identity. Primary project is
* provenance only; the charges below are the admitted project set.
* provenance only. `admittedProjectIds` is the complete admitted set, sorted and
* immutable (trigger), kept after settlement without project foreign keys. Null
* means unknown: settled before the set was recorded. Charges below are the
* active capacity only.
*/
export const executionAdmissions = pgTable("execution_admissions", {
id: uuid("id").primaryKey(),
@@ -33,6 +36,7 @@ export const executionAdmissions = pgTable("execution_admissions", {
attempt: integer("attempt").notNull(),
primaryProjectId: uuid("primary_project_id"),
contextRevision: text("context_revision").notNull(),
admittedProjectIds: uuid("admitted_project_ids").array(),
settlementId: uuid("settlement_id"),
settledAt: timestamp("settled_at", { withTimezone: true }),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
@@ -42,6 +46,8 @@ export const executionAdmissions = pgTable("execution_admissions", {
liveRun: uniqueIndex("execution_admissions_live_run_uq").on(t.runId).where(sql`${t.settledAt} is null`),
attemptCheck: check("execution_admissions_attempt_check", sql`${t.attempt} > 0`),
settlementCheck: check("execution_admissions_settlement_check", sql`(${t.settlementId} is null) = (${t.settledAt} is null)`),
projectSetCheck: check("execution_admissions_project_set_check", sql`case when ${t.admittedProjectIds} is null
then ${t.settledAt} is not null else array_position(${t.admittedProjectIds}, null) is null and cardinality(${t.admittedProjectIds}) <= 100 end`),
}));
/** One active charge per distinct admitted project. Settlement deletes the whole set; a charged project cannot be deleted. */
@@ -11,6 +11,7 @@ import {
canonicalNativeRuntimeContextDigest,
composeNativeSystemInstructions,
nativeRuntimePromptDigest,
type NativeRuntimeContextSnapshot,
} from "./runtime-context.js";
const input: NativeExecutionInputV1 = {
@@ -81,6 +82,7 @@ describe("NativeExecutionInputV1", () => {
runtimeContext: { ...context, aggregateDigest: canonicalNativeRuntimeContextDigest(context) },
}));
const recovered = parseNativeExecutionInput(persisted);
if (!("runtimeContext" in recovered)) throw new Error("Expected a runtime context");
expect(recovered.runtimeContext).toEqual(persisted.runtimeContext);
expect(parseNativeExecutionInput(recovered)).toEqual(recovered);
},
@@ -88,7 +90,7 @@ describe("NativeExecutionInputV1", () => {
it("parses v3 immutable runtime context without changing the model task envelope", () => {
const digest = "0".repeat(64);
const context = {
const context: Omit<NativeRuntimeContextSnapshot, "aggregateDigest"> = {
prompt: { revision: PAPERCLIP_EXECUTION_PROMPT_REVISION, text: PAPERCLIP_EXECUTION_PROMPT, digest: nativeRuntimePromptDigest() },
instructions: {
entryPath: "AGENTS.md",
@@ -101,7 +103,7 @@ describe("NativeExecutionInputV1", () => {
bundle: { schema: NATIVE_RUNTIME_ASSET_SCHEMA, digest, manifestDigest: digest, rootPath: "/runtime/skills/research", fileCount: 2, totalBytes: 42 },
}],
mcp: { assignmentSetId: "sha256:test", digest, bindingId: "native-mcp:run-1" },
} as const;
};
const preconfigured = {
...input, schema: "paperclip.native-execution-input.v7", homeMode: "preconfigured",
provider: { kind: "codex", model: null, approvalPolicy: "never" },
@@ -129,6 +131,7 @@ describe("NativeExecutionInputV1", () => {
runtimeContext: { ...context, aggregateDigest: canonicalNativeRuntimeContextDigest(context) },
});
expect(parsed.schema).toBe("paperclip.native-execution-input.v3");
if (!("runtimeContext" in parsed)) throw new Error("Expected a runtime context");
const envelope = buildNativeModelEnvelope(parsed);
expect(envelope.task).toEqual(buildNativeModelEnvelope(input).task);
expect(envelope.completionContract).toEqual(buildNativeModelEnvelope(input).completionContract);
@@ -277,7 +280,7 @@ describe("NativeExecutionInputV1", () => {
});
it("deserializes pre-provider Codex state as Codex", () => {
const legacy = structuredClone(input) as Record<string, unknown>;
const legacy: Record<string, unknown> = { ...structuredClone(input) };
delete legacy.provider;
expect(parseNativeExecutionInput(legacy).provider).toEqual({
kind: "codex",
@@ -448,9 +451,7 @@ describe("NativeExecutionInputV1", () => {
});
it.each([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40] as const)("decodes closed Copilot profile history at version %s without admitting a future version", (agentProfileVersion) => {
const { qualificationModel: _qualificationModel, reportedModelId: _reportedModelId,
permissionPolicy, qualificationStatus: _qualificationStatus, modelPolicy: _modelPolicy,
...snapshot } = QUALIFIED_ACPX_PROFILES.copilot;
const { permissionPolicy, qualificationStatus: _qualificationStatus, ...snapshot } = QUALIFIED_ACPX_PROFILES.copilot;
const provider = {
kind: "acpx", agent: "copilot", model: "gpt-5.6-luna", permissionPolicy,
profile: { ...snapshot, agentProfileVersion },
@@ -474,11 +475,10 @@ describe("NativeExecutionInputV1", () => {
it.each(Object.values(QUALIFIED_ACPX_PROFILES))(
"admits the current $agent profile through the native execution boundary",
(declaration) => {
const { qualificationModel, reportedModelId: _reported, permissionPolicy,
modelPolicy: _modelPolicy, qualificationStatus: _status, ...profile } = declaration;
const { permissionPolicy, qualificationStatus: _status, ...profile } = declaration;
const provider = {
kind: "acpx", agent: declaration.agent,
model: qualificationModel || "explicit-provider-model",
model: "explicit-provider-model",
permissionPolicy, profile,
...(declaration.agent === "pi" ? { piThinkingLevel: "low" } : {}),
};
@@ -493,7 +493,7 @@ describe("NativeExecutionInputV1", () => {
);
it("defaults legacy lifecycle state to per-turn and validates warm timeouts", () => {
const legacy = structuredClone(input) as Record<string, unknown>;
const legacy: Record<string, unknown> = { ...structuredClone(input) };
delete (legacy.session as Record<string, unknown>).lifecyclePolicy;
expect(parseNativeExecutionInput(legacy).session.lifecyclePolicy).toEqual({
mode: "per_turn",
@@ -566,11 +566,11 @@ describe("NativeExecutionInputV2 planning", () => {
})).toMatchObject({ task: { workMode: "planning" }, executionMode: "default" });
});
const cloudContext = {
const cloudContext: Omit<NativeRuntimeContextSnapshot, "aggregateDigest"> = {
prompt: { revision: PAPERCLIP_EXECUTION_PROMPT_REVISION, text: PAPERCLIP_EXECUTION_PROMPT, digest: nativeRuntimePromptDigest() },
instructions: { entryPath: "AGENTS.md", bundle: { schema: NATIVE_RUNTIME_ASSET_SCHEMA, digest: "0".repeat(64), manifestDigest: "0".repeat(64), rootPath: "/runtime/instructions", fileCount: 1, totalBytes: 1 } },
skills: [], mcp: { assignmentSetId: "none", digest: "0".repeat(64), bindingId: null },
} as const;
};
const cloudPlanning = {
...planning, schema: "paperclip.native-execution-input.v5",
provider: { kind: "cursor_cloud", model: "explicit-cloud-model", cursorCloud: { schema: "paperclip.cursor-cloud.config.v1", repos: [{ url: "https://github.com/example/fixture" }] } },
@@ -639,12 +639,12 @@ describe.each(["ask", "skill_test"])("NativeExecutionInputV2 %s mode", (workMode
describe("native task context ownership", () => {
function currentInput() {
const digest = "0".repeat(64);
const context = {
const context: Omit<NativeRuntimeContextSnapshot, "aggregateDigest"> = {
prompt: { revision: PAPERCLIP_EXECUTION_PROMPT_REVISION, text: PAPERCLIP_EXECUTION_PROMPT, digest: nativeRuntimePromptDigest() },
instructions: { entryPath: "AGENTS.md", bundle: { schema: NATIVE_RUNTIME_ASSET_SCHEMA, digest, manifestDigest: digest, rootPath: "/runtime/instructions", fileCount: 1, totalBytes: 1 } },
skills: [],
mcp: { assignmentSetId: "none", digest, bindingId: null },
} as const;
};
return parseNativeExecutionInput({
...input,
schema: NATIVE_EXECUTION_INPUT_SCHEMA,
@@ -656,8 +656,7 @@ describe("native task context ownership", () => {
});
}
const { qualificationModel: _copilotQualificationModel, reportedModelId: _copilotReportedModelId,
permissionPolicy: _copilotPermissionPolicy, qualificationStatus: _copilotQualificationStatus, modelPolicy: _copilotModelPolicy,
const { permissionPolicy: _copilotPermissionPolicy, qualificationStatus: _copilotQualificationStatus,
...copilotSnapshot } = QUALIFIED_ACPX_PROFILES.copilot;
it.each([
@@ -690,6 +689,7 @@ describe("native task context ownership", () => {
} : {}),
}));
const recovered = parseNativeExecutionInput(persisted);
if (!("runtimeContext" in recovered)) throw new Error("Expected a runtime context");
expect(recovered.runtimeContext).toEqual(context);
expect(recovered.provider).toEqual(provider);
expect(recovered.session.driverKind).toBe(driverKind);
@@ -759,6 +759,7 @@ describe("native task context ownership", () => {
const fullResume = buildNativeModelEnvelope(parsed, { resumedSession: true });
expect(fullResume.schema).toBe(envelopeSchema);
if (!("task" in fullResume)) throw new Error("Expected a full resume envelope");
expect(fullResume.task.prompt).toBe(parsed.task.prompt);
expect(fullResume.task.prompt).not.toContain(guidance);
@@ -796,6 +797,7 @@ describe("native task context ownership", () => {
completionContract: { ...execution.completionContract, contract },
completionSources: { promptSha256: createHash("sha256").update(execution.task.prompt).digest("hex"), contractRevision: contract.revision, criteria: [{ id: "objective", source }] },
});
if (completeInput.schema !== NATIVE_EXECUTION_INPUT_SCHEMA) throw new Error("Expected a v5 execution input");
const before = structuredClone(completeInput.completionContract);
const envelope = buildNativeModelEnvelope(completeInput);
expect(envelope.completionContract.criteria).toEqual([
@@ -815,7 +817,7 @@ describe("native task context ownership", () => {
describe("Claude effort input v9", () => {
const { permissionPolicy: _permission, qualificationModel: _model, reportedModelId: _reported, ...profile } = QUALIFIED_ACPX_PROFILES.claude;
const { permissionPolicy: _permission, ...profile } = QUALIFIED_ACPX_PROFILES.claude;
const digest = "0".repeat(64);
const context = { prompt: { revision: PAPERCLIP_EXECUTION_PROMPT_REVISION, text: PAPERCLIP_EXECUTION_PROMPT, digest: nativeRuntimePromptDigest() },
instructions: { entryPath: "AGENTS.md", bundle: { schema: NATIVE_RUNTIME_ASSET_SCHEMA, digest, manifestDigest: digest, rootPath: "/runtime/instructions", fileCount: 1, totalBytes: 1 } },
@@ -856,7 +858,7 @@ describe("Claude effort input v9", () => {
});
describe("Grok effort input v10", () => {
const { permissionPolicy: _permission, qualificationModel: _model, reportedModelId: _reported, ...profile } = QUALIFIED_ACPX_PROFILES.grok;
const { permissionPolicy: _permission, ...profile } = QUALIFIED_ACPX_PROFILES.grok;
const digest = "0".repeat(64);
const context = { prompt: { revision: PAPERCLIP_EXECUTION_PROMPT_REVISION, text: PAPERCLIP_EXECUTION_PROMPT, digest: nativeRuntimePromptDigest() },
instructions: { entryPath: "AGENTS.md", bundle: { schema: NATIVE_RUNTIME_ASSET_SCHEMA, digest, manifestDigest: digest, rootPath: "/runtime/instructions", fileCount: 1, totalBytes: 1 } },
+30 -6
View File
@@ -11,6 +11,7 @@
// cycle the largest import cycle, reading `below` as declarations
// above files that depend on the cycle, reading both as declarations
// tests test files, reading all three as declarations
// test-tree-N src/__tests__, in shards, reading the source layers as declarations
//
// The layers come from the import graph on every run, so nothing has to move
// and no list can go stale. The import scan does not have to be perfect: if it
@@ -22,21 +23,26 @@
//
// Projects outside the server read the source layers as declarations through a
// `tsconfig.layers.json` that references tsconfig.declarations.json in the output
// directory. --declarations-only builds those layers and skips the tests layer.
// directory. --declarations-only builds those layers and skips the test layers.
//
// Usage: node scripts/typecheck-layers.mjs [--declarations-only]
import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { crc32 } from "node:zlib";
import { runTsc } from "../../scripts/run-tsc.mjs";
const IMPORT_SPECIFIER = /(?:from\s+|import\s*\(\s*|import\s+)["'](\.[^"']+)["']/g;
const OUTPUT_DIRECTORY = path.join("node_modules", ".cache", "paperclip-typecheck");
// One program over this tree peaks at 3.9 GiB on one thread. Three peak at 2.5 GiB.
const TEST_TREE_SHARDS = 3;
export const isTestFile = (file) => /\.test\.tsx?$/.test(file) || file.split("/").includes("__tests__");
// tsconfig.json excludes this tree. A test reaches its helpers through imports.
const isExcludedFromProgram = (file) => file.startsWith("src/__tests__/");
// tsconfig.json excludes this tree, so the test-tree layers check it.
const isTestTreeFile = (file) => file.startsWith("src/__tests__/");
// Test layers emit nothing, so other projects cannot read them as declarations.
const emitsDeclarations = (layer) => layer.name !== "tests" && !layer.testTree;
export function listSourceFiles(serverRoot) {
const files = [];
@@ -174,7 +180,12 @@ export function planLayers(files) {
const dependents = reachableFrom(cycle, reversed);
const above = [...dependents].filter((file) => !cycle.has(file));
const below = [...graph.keys()].filter((file) => !dependents.has(file));
const tests = [...candidates.keys()].filter((file) => isTestFile(file) && !isExcludedFromProgram(file));
const tests = [...candidates.keys()].filter((file) => isTestFile(file) && !isTestTreeFile(file));
// A hash keeps each file in its shard when other files are added or removed.
const testTree = Array.from({ length: TEST_TREE_SHARDS }, () => []);
for (const file of candidates.keys()) {
if (isTestTreeFile(file)) testTree[crc32(file) % TEST_TREE_SHARDS].push(file);
}
const layer = (name, members, references) => {
const sorted = [...members].sort();
@@ -186,6 +197,10 @@ export function planLayers(files) {
layer("cycle", cycle, ["below"]),
layer("above", above, ["below", "cycle"]),
layer("tests", tests, ["below", "cycle", "above"]),
...testTree.map((shard, index) => ({
...layer(`test-tree-${index}`, shard, ["below", "cycle", "above"]),
testTree: true,
})),
].filter((candidate) => candidate.sourceFiles.length > 0);
}
@@ -195,6 +210,15 @@ export function layerConfig(layer, { emptyLayers = new Set() } = {}) {
const references = layer.references
.filter((name) => !emptyLayers.has(name))
.map((name) => ({ path: `./tsconfig.${name}.json` }));
// Editors read the same options from this file.
if (layer.testTree) {
return {
extends: fromOutput("src/__tests__/tsconfig.json"),
files: layer.files.map(fromOutput),
include: [],
references,
};
}
const output =
layer.name === "tests"
? { composite: false, declaration: false, declarationMap: false, sourceMap: false, noEmit: true }
@@ -222,7 +246,7 @@ export function declarationsConfig(layers) {
compilerOptions: { composite: true },
files: [],
references: layers
.filter((layer) => layer.name !== "tests")
.filter(emitsDeclarations)
.map((layer) => ({ path: `./tsconfig.${layer.name}.json` })),
};
}
@@ -259,7 +283,7 @@ export function typecheckLayers({
let exitCode = 0;
for (const layer of layers) {
if (declarationsOnly && layer.name === "tests") continue;
if (declarationsOnly && !emitsDeclarations(layer)) continue;
log(`typecheck:layers: ${layer.name} (${layer.sourceFiles.length} files)`);
const config = path.join(OUTPUT_DIRECTORY, `tsconfig.${layer.name}.json`);
const { status, signal } = run(["-p", config], { cwd: serverRoot });
+102 -6
View File
@@ -76,7 +76,7 @@ describe("planLayers", () => {
for (const layer of layers) expect(layer.files).toContain("src/types/ambient.d.ts");
});
it("keeps test support files in the test layer and leaves src/__tests__ to imports", () => {
it("keeps test support files in the test layer and shards src/__tests__ by a stable hash", () => {
const layers = planLayers(
new Map([
["src/a.ts", ""],
@@ -88,6 +88,25 @@ describe("planLayers", () => {
expect(sourcesOf(layers, "below")).toEqual(["src/a.ts"]);
expect(sourcesOf(layers, "tests")).toEqual(["src/services/__tests__/fixtures.ts"]);
const shards = layers.filter((layer) => layer.testTree);
expect(shards.flatMap((layer) => layer.sourceFiles).sort()).toEqual([
"src/__tests__/big.test.ts",
"src/__tests__/helpers/db.ts",
]);
for (const shard of shards) expect(shard.references).toEqual(["below", "cycle", "above"]);
const withMore = planLayers(
new Map([
["src/a.ts", ""],
["src/__tests__/helpers/db.ts", ""],
["src/__tests__/big.test.ts", ""],
["src/__tests__/added-1.test.ts", ""],
["src/__tests__/added-2.test.ts", ""],
]),
);
for (const shard of shards) {
for (const file of shard.sourceFiles) expect(sourcesOf(withMore, shard.name)).toContain(file);
}
});
it("lists imported JSON in the layer that imports it", () => {
@@ -120,14 +139,14 @@ describe("planLayers", () => {
const planned = layers.flatMap((layer) => layer.sourceFiles);
expect(new Set(planned).size).toBe(planned.length);
expect(planned.sort()).toEqual(
sourceFiles.filter((file) => !file.endsWith(".d.ts") && !file.startsWith("src/__tests__/")).sort(),
sourceFiles.filter((file) => !file.endsWith(".d.ts")).sort(),
);
const position = new Map(
layers.flatMap((layer, index) => layer.sourceFiles.map((file) => [file, index])),
);
const imports = readImports(new Map([...files].filter(([file]) => !file.endsWith(".d.ts"))));
for (const layer of layers.filter((candidate) => candidate.name !== "tests")) {
for (const layer of layers.filter((candidate) => candidate.name !== "tests" && !candidate.testTree)) {
for (const file of layer.sourceFiles) {
for (const target of imports.get(file).modules) {
if (position.has(target)) expect(position.get(target)).toBeLessThanOrEqual(position.get(file));
@@ -160,9 +179,37 @@ describe("layerConfig", () => {
});
});
describe("layerConfig for the test tree", () => {
it("reads the options that editors read and emits nothing", () => {
const config = layerConfig({
name: "test-tree-0",
files: ["src/__tests__/a.test.ts"],
references: ["below"],
testTree: true,
});
expect(config.extends).toBe("../../../src/__tests__/tsconfig.json");
expect(config.compilerOptions).toBeUndefined();
expect(config.files).toEqual(["../../../src/__tests__/a.test.ts"]);
expect(config.references).toEqual([{ path: "./tsconfig.below.json" }]);
});
it("has a real tests tsconfig that cannot emit and sees the ambient types", () => {
const config = JSON.parse(readFileSync(path.join(serverRoot, "src/__tests__/tsconfig.json"), "utf8"));
expect(config.compilerOptions).toMatchObject({ noEmit: true, rootDir: "../../.." });
expect(config.include).toContain("../types/*.d.ts");
});
});
describe("declarationsConfig", () => {
it("references the source layers that exist, and not tests", () => {
const config = declarationsConfig([{ name: "below" }, { name: "above" }, { name: "tests" }]);
it("references the source layers that exist, and no test layer", () => {
const config = declarationsConfig([
{ name: "below" },
{ name: "above" },
{ name: "tests" },
{ name: "test-tree-0", testTree: true },
]);
expect(config.compilerOptions.composite).toBe(true);
expect(config.files).toEqual([]);
@@ -213,8 +260,10 @@ describe("typecheckLayers", () => {
expect(JSON.parse(readFileSync(path.join(output, "package.json"), "utf8"))).toEqual({ type: "module" });
});
it("skips the tests layer when only declarations are needed", () => {
it("skips the test layers when only declarations are needed", () => {
const root = serverFixture();
mkdirSync(path.join(root, "src", "__tests__"));
writeFileSync(path.join(root, "src", "__tests__", "route.test.ts"), `import "../route.js";`);
const configs = [];
const status = typecheckLayers({
@@ -405,6 +454,53 @@ describe("typecheckLayers with the real compiler", () => {
expect(output).toContain("leaf");
}, 60_000);
function addTestTree(root, source) {
mkdirSync(path.join(root, "src", "__tests__"), { recursive: true });
writeFileSync(
path.join(root, "src", "__tests__", "tsconfig.json"),
`${JSON.stringify({
extends: "../../tsconfig.json",
compilerOptions: { noEmit: true, rootDir: "../.." },
include: ["."],
})}\n`,
);
writeFileSync(path.join(root, "src", "__tests__", "tree.test.ts"), source);
}
it("checks the test tree through declarations", () => {
const root = compilerFixture();
addTestTree(root, `import { route } from "../route.js";\nexport const checked: number = route();\n`);
const { status, output } = check(root);
expect(output).toBe("");
expect(status).toBe(0);
}, 60_000);
it("fails on a type error in the test tree", () => {
const root = compilerFixture();
addTestTree(root, `import { route } from "../route.js";\nexport const checked: string = route();\n`);
const { status, output } = check(root);
expect(status).not.toBe(0);
expect(output).toMatch(/tree\.test\.ts\(2,\d+\): error TS2322/);
}, 60_000);
it("fails when the test-tree tsconfig is invalid", () => {
const root = compilerFixture();
addTestTree(root, `export const checked = 1;\n`);
writeFileSync(
path.join(root, "src", "__tests__", "tsconfig.json"),
`${JSON.stringify({ extends: "../../tsconfig.json", compilerOptions: { noEmit: true, rootDir: "../..", target: "not-a-target" } })}\n`,
);
const { status, output } = check(root);
expect(status).not.toBe(0);
expect(output).toMatch(/error TS\d+/);
}, 60_000);
it("reports a type change through declarations in the test layer", () => {
const root = compilerFixture();
writeFileSync(
+39
View File
@@ -0,0 +1,39 @@
// Server tests reach Daytona plugin source, but the root install does not install
// that plugin's dependencies. Its own project checks that source against the real
// package. The server tests only need these names to resolve. Async calls return a
// Promise because a bare `any` makes the plugin's generic helpers infer `unknown`.
type AsyncMembers = { [member: string]: (...args: any[]) => Promise<any> };
export declare class Daytona {
constructor(config?: DaytonaConfig);
snapshot: AsyncMembers;
get(...args: any[]): Promise<any>;
create(...args: any[]): Promise<any>;
list(...args: any[]): Promise<any>;
[member: string]: any;
}
export declare class DaytonaNotFoundError extends Error {}
export declare class DaytonaTimeoutError extends Error {}
export type CreateSandboxBaseParams = any;
export type CreateSandboxFromImageParams = any;
export type CreateSandboxFromSnapshotParams = any;
export type DaytonaConfig = any;
export type FileDownloadRequest = any;
export type FileDownloadResponse = any;
export type FileUpload = any;
export type Resources = any;
export interface Sandbox {
fs: { downloadFiles(...args: any[]): Promise<any[]>; [member: string]: (...args: any[]) => Promise<any> };
process: {
createPty: (...args: any[]) => Promise<any>;
executeCommand(...args: any[]): Promise<{ result?: string; artifacts?: { stdout?: string }; exitCode?: number }>;
getSessionCommandLogs(
sessionId: string,
commandId: string,
onStdout?: (chunk: string) => void,
onStderr?: (chunk: string) => void,
): Promise<any>;
[member: string]: (...args: any[]) => Promise<any>;
};
[member: string]: any;
}
@@ -201,7 +201,7 @@ describeEmbeddedPostgres("access routes permissions upgrade compatibility", () =
companyId: company.id,
applicationKey: `route-app-${randomUUID()}`,
name: "Route personal app",
type: "mcp",
type: "mcp_http",
status: "active",
}).returning().then((rows) => rows[0]!);
const connection = await db.insert(toolConnections).values({
+7 -7
View File
@@ -272,7 +272,7 @@ describeEmbeddedPostgres("access service", () => {
companyId: company.id,
applicationKey: `instance-access-${randomUUID()}`,
name: "Instance access app",
type: "mcp",
type: "mcp_http",
status: "active",
}).returning().then((rows) => rows[0]!);
const connection = await db.insert(toolConnections).values({
@@ -333,7 +333,7 @@ describeEmbeddedPostgres("access service", () => {
companyId: company.id,
applicationKey: `app-${randomUUID()}`,
name: "Personal mail",
type: "mcp",
type: "mcp_http",
status: "active",
}).returning().then((rows) => rows[0]!);
const connection = await db.insert(toolConnections).values({
@@ -428,7 +428,7 @@ describeEmbeddedPostgres("access service", () => {
companyId: company.id,
applicationKey: `shared-personal-${randomUUID()}`,
name: "Shared personal app",
type: "mcp",
type: "mcp_http",
status: "active",
}).returning().then((rows) => rows[0]!);
const connection = await db.insert(toolConnections).values({
@@ -505,7 +505,7 @@ describeEmbeddedPostgres("access service", () => {
companyId: company.id,
applicationKey: `mixed-${randomUUID()}`,
name: "Mixed credential app",
type: "mcp",
type: "mcp_http",
status: "active",
}).returning().then((rows) => rows[0]!);
const definition = await db.insert(userSecretDefinitions).values({
@@ -595,7 +595,7 @@ describeEmbeddedPostgres("access service", () => {
companyId: company.id,
applicationKey: `shared-org-${randomUUID()}`,
name: "Shared organization app",
type: "mcp",
type: "mcp_http",
status: "active",
}).returning().then((rows) => rows[0]!);
const connection = await db.insert(toolConnections).values({
@@ -692,7 +692,7 @@ describeEmbeddedPostgres("access service", () => {
companyId: company.id,
applicationKey: `app-${randomUUID()}`,
name: "Personal mail",
type: "mcp",
type: "mcp_http",
status: "active",
}).returning().then((rows) => rows[0]!);
const connection = await db.insert(toolConnections).values({
@@ -754,7 +754,7 @@ describeEmbeddedPostgres("access service", () => {
companyId: company.id,
applicationKey: `app-${randomUUID()}`,
name: "Personal shared credentials",
type: "mcp",
type: "mcp_http",
status: "active",
}).returning().then((rows) => rows[0]!);
const definitions = await db.insert(userSecretDefinitions).values([
@@ -75,7 +75,7 @@ describe("resolveResponsibleUserIdForActivity", () => {
});
it("prefers the heartbeat run responsible user", async () => {
const db = createReader(new Map([
const db = createReader(new Map<unknown, Array<Record<string, unknown>>>([
[heartbeatRuns, [{ responsibleUserId: "run-user" }]],
[issues, [{ responsibleUserId: "issue-user", createdByUserId: null }]],
[agentApiKeys, [{ responsibleUserId: "key-user" }]],
@@ -89,7 +89,7 @@ describe("resolveResponsibleUserIdForActivity", () => {
});
it("falls back to issue attribution when the run is unavailable", async () => {
const db = createReader(new Map([
const db = createReader(new Map<unknown, Array<Record<string, unknown>>>([
[heartbeatRuns, []],
[issues, [{ responsibleUserId: "issue-user", createdByUserId: "creator-user" }]],
[agentApiKeys, [{ responsibleUserId: "key-user" }]],
@@ -103,7 +103,7 @@ describe("resolveResponsibleUserIdForActivity", () => {
});
it("uses explicit issue context for non-issue activity", async () => {
const db = createReader(new Map([
const db = createReader(new Map<unknown, Array<Record<string, unknown>>>([
[issues, [{ responsibleUserId: "issue-user", createdByUserId: null }]],
[companies, [{ defaultResponsibleUserId: "default-user" }]],
]));
@@ -116,7 +116,7 @@ describe("resolveResponsibleUserIdForActivity", () => {
});
it("uses the active agent API key responsible user for no-run actions", async () => {
const db = createReader(new Map([
const db = createReader(new Map<unknown, Array<Record<string, unknown>>>([
[agentApiKeys, [{ responsibleUserId: "key-user" }]],
[companies, [{ defaultResponsibleUserId: "default-user" }]],
]));
@@ -129,7 +129,7 @@ describe("resolveResponsibleUserIdForActivity", () => {
});
it("falls back to the company default responsible user", async () => {
const db = createReader(new Map([
const db = createReader(new Map<unknown, Array<Record<string, unknown>>>([
[agentApiKeys, []],
[companies, [{ defaultResponsibleUserId: "default-user" }]],
]));
@@ -142,7 +142,7 @@ describe("resolveResponsibleUserIdForActivity", () => {
});
it("uses issue creator attribution when responsibleUserId is absent", async () => {
const db = createReader(new Map([
const db = createReader(new Map<unknown, Array<Record<string, unknown>>>([
[issues, [{ responsibleUserId: null, createdByUserId: "creator-user" }]],
[companies, [{ defaultResponsibleUserId: "default-user" }]],
]));
@@ -151,7 +151,7 @@ describe("resolveResponsibleUserIdForActivity", () => {
});
it("ignores malformed UUID-backed identifiers", async () => {
const db = createReader(new Map([
const db = createReader(new Map<unknown, Array<Record<string, unknown>>>([
[heartbeatRuns, [{ responsibleUserId: "run-user" }]],
[issues, [{ responsibleUserId: "issue-user", createdByUserId: null }]],
[agentApiKeys, [{ responsibleUserId: "key-user" }]],
+2 -1
View File
@@ -4,6 +4,7 @@ import request from "supertest";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { vi } from "vitest";
import type { ServerAdapterModule } from "../adapters/index.js";
import type { RunnerTarget } from "../services/agent-runner-selection.js";
const mockAdapterPluginStore = vi.hoisted(() => ({
listAdapterPlugins: vi.fn(),
@@ -221,7 +222,7 @@ describe("adapter routes", () => {
});
});
it.each([
it.each<[RunnerTarget, string]>([
[{ driver: "ssh", platform: "linux", architecture: "x64" }, "paperclip"],
[{ driver: "ssh", platform: "darwin", architecture: "arm64" }, "paperclip"],
[{ driver: "ssh", platform: "darwin", architecture: "x64" }, "paperclip"],
@@ -177,12 +177,12 @@ describePostgres("agent action audit routes", () => {
it("denies agents and board users without the audit permission", async () => {
const { company, agent } = await seed();
const agentResponse = await request(await createApp(db, {
type: "agent", agentId: agent.id, companyId: company.id, runId: null, source: "agent_jwt",
type: "agent", agentId: agent.id, companyId: company.id, source: "agent_jwt",
})).get(`/api/companies/${company.id}/audit/agent-actions`);
expect(agentResponse.status).toBe(403);
const agentAllActorsResponse = await request(await createApp(db, {
type: "agent", agentId: agent.id, companyId: company.id, runId: null, source: "agent_jwt",
type: "agent", agentId: agent.id, companyId: company.id, source: "agent_jwt",
})).get(`/api/companies/${company.id}/audit/agent-actions?actorScope=all`);
expect(agentAllActorsResponse.status, JSON.stringify(agentAllActorsResponse.body)).toBe(200);
expect(agentAllActorsResponse.body.items).toHaveLength(3);
@@ -307,7 +307,7 @@ describe("agent auth middleware", () => {
it.each(["key", "jwt"])("rejects unfinished setup authentication with a %s", async kind => {
const companyId = randomUUID();
const agentId = randomUUID();
const token = kind === "jwt" ? createLocalAgentJwt(agentId, companyId, "codex_local", randomUUID(), "user-1") : "pcp_setup-test-token";
const token = kind === "jwt" ? createLocalAgentJwt(agentId, companyId, "codex_local", randomUUID(), "user-1")! : "pcp_setup-test-token";
const { db } = createDbState({
agent: { id: agentId, companyId, status: "paused", lifecycleHolds: ["setup"] },
...(kind === "key" ? { agentKey: { id: randomUUID(), agentId, companyId, keyHash: hashToken(token), responsibleUserId: "user-1" } } : {}),
@@ -147,7 +147,7 @@ import { recoveryPostgresSupport as support } from "./helpers/recovery-postgres.
const locked = new Promise<number>(resolve => { ready = resolve; });
const release = new Promise<void>(resolve => { unlock = resolve; });
const gate = server.db.transaction(async tx => {
const [{ pid }] = await tx.execute(sql<{ pid: number }>`select pg_backend_pid() as pid`);
const [{ pid }] = await tx.execute<{ pid: number }>(sql`select pg_backend_pid() as pid`);
await tx.select({ id: issues.id }).from(issues).where(eq(issues.id, receipt.conversationId)).for("update");
ready(pid); await release;
});
@@ -263,7 +263,7 @@ import { recoveryPostgresSupport as support } from "./helpers/recovery-postgres.
const locked = new Promise<number>(resolve => { ready = resolve; });
const release = new Promise<void>(resolve => { unlock = resolve; });
const gate = server.db.transaction(async tx => {
const [{ pid }] = await tx.execute(sql<{ pid: number }>`select pg_backend_pid() as pid`);
const [{ pid }] = await tx.execute<{ pid: number }>(sql`select pg_backend_pid() as pid`);
await tx.select({ id: agents.id }).from(agents).where(eq(agents.id, f.agentId)).for("update");
ready(pid);
await release;
@@ -71,7 +71,7 @@ describe("agent commentary browsing", () => {
await db.insert(agents).values({ id: agentId, companyId: pagedCompanyId, name: "Writer", role: "engineer", adapterType: "codex_local" });
await db.insert(heartbeatRuns).values({ id: runId, companyId: pagedCompanyId, agentId, status: "succeeded" });
await db.insert(agentCommentary).values(Array.from({ length: 58 }, (_, index) => ({
companyId: pagedCompanyId, agentId, runId, kind: "suggestion", body: `Message ${index}`,
companyId: pagedCompanyId, agentId, runId, kind: "suggestion" as const, body: `Message ${index}`,
idempotencyKey: String(index), payloadHash: "hash",
})));
const first = await reader.list(pagedCompanyId, query(), sql`true`);
@@ -51,6 +51,7 @@ import {
undeliveredConversationComments,
} from "../services/agent-conversations.js";
import { classifyIssueGraphLiveness } from "../services/recovery/issue-graph-liveness.js";
import { NATIVE_STATUS_ARBITER_POLICY_VERSION } from "../services/native-runtime/status-arbiter.js";
import { runningProcesses } from "../adapters/index.js";
import {
buildPaperclipTaskMarkdown,
@@ -444,9 +445,9 @@ import {
});
const [interaction] = await db.insert(issueThreadInteractions).values({
companyId, issueId: issue.id, kind: "request_confirmation", status: "rejected",
payload: { version: 1, target: { type: "issue_document", key: "plan", documentId: document.id,
payload: { version: 1, prompt: "Approve the plan?", target: { type: "issue_document", key: "plan", documentId: document.id,
revisionId: document.latestRevisionId!, revisionNumber: document.latestRevisionNumber } },
result: { outcome: "rejected", reason: "Include CHAT_REVIEW_MARKER in the revised plan." },
result: { version: 1, outcome: "rejected", reason: "Include CHAT_REVIEW_MARKER in the revised plan." },
}).returning();
const input = { db, companyId, issueSummary: { ...issue, workMode: "planning" },
contextSnapshot: { issueId: issue.id, conversationMode: true, interactionId: interaction!.id,
@@ -467,7 +468,7 @@ import {
contextSnapshot: { issueId: issue.id, conversationMode: true } });
expect(later?.planReviewContext).toBeNull();
// An unrelated confirmation must not cause old plan context to be replayed.
await db.update(issueThreadInteractions).set({ payload: { version: 1 } })
await db.update(issueThreadInteractions).set({ payload: { version: 1, prompt: "Proceed?" } })
.where(eq(issueThreadInteractions.id, interaction!.id));
expect((await buildPaperclipWakePayload(input))?.planReviewContext).toBeNull();
});
@@ -769,7 +770,7 @@ import {
const succeeded = { ...run, contextSnapshot: prepared.context, status: "succeeded" };
const [interaction] = await db.insert(issueThreadInteractions).values({
companyId, issueId: chat.id, sourceRunId: run.id, createdByAgentId: agentId,
kind: "request_confirmation", status: "answered", payload: { version: 1 },
kind: "request_confirmation", status: "answered", payload: { version: 1, prompt: "Proceed?" },
}).returning();
expect(await settleConversationTurn(db, succeeded)).toBe(false);
await db.update(issueThreadInteractions).set({ status: "pending", sourceRunId: null })
@@ -831,8 +832,8 @@ import {
blockedByIssueIds: [blocker.id],
});
for (const state of [
{ status: "in_review", conversationState: "waiting" },
{ status: "blocked", conversationState: "active" },
{ status: "in_review", conversationState: "waiting" as const },
{ status: "blocked", conversationState: "active" as const },
]) {
await db.update(issues).set(state).where(eq(issues.id, chat.id));
for (const reason of ["issue_blockers_resolved", "issue_children_completed", "issue_unblock_requested"]) {
@@ -914,19 +915,21 @@ import {
const answerRun = await runFor(chat.id, first.id, { source: "issue.interaction.respond", interactionId: question.id,
interactionKind: "ask_user_questions", interactionStatus: "answered" });
const prepared = await prepareConversationTurn(db, answerRun);
expect(prepared.context.wakeCommentId).toBe(first.id);
expect(prepared.context.conversationReplyBoundaryCommentId).toBe(["answered", "new-message"].includes(scenario) ? newer.id : first.id);
const preparedContext: Record<string, unknown> = prepared.context;
expect(preparedContext.wakeCommentId).toBe(first.id);
expect(preparedContext.conversationReplyBoundaryCommentId).toBe(["answered", "new-message"].includes(scenario) ? newer.id : first.id);
// A retry reuses the frozen boundary, even if another message has arrived.
if (scenario === "new-message") await svc.addComment(chat.id, "New instructions while you answer", { userId: "local-board" });
const retry = await prepareConversationTurn(db, { ...answerRun, contextSnapshot: prepared.context });
expect(retry.context.conversationReplyBoundaryCommentId).toBe(prepared.context.conversationReplyBoundaryCommentId);
const retryContext: Record<string, unknown> = retry.context;
expect(retryContext.conversationReplyBoundaryCommentId).toBe(preparedContext.conversationReplyBoundaryCommentId);
await svc.addComment(chat.id, "You chose Blue.", { agentId, runId: answerRun.id });
expect(await settleConversationTurn(db, { ...answerRun, status: "succeeded", contextSnapshot: prepared.context })).toBe(true);
const [settled] = await db.select().from(issues).where(eq(issues.id, chat.id));
expect(settled.conversationState).toBe(scenario === "answered" ? "waiting" : "active");
expect(retry.context.conversationReplayThroughCommentId).toBe(prepared.context.conversationReplayThroughCommentId);
expect(retryContext.conversationReplayThroughCommentId).toBe(preparedContext.conversationReplayThroughCommentId);
const replay = await conversationReplay(db, companyId, chat.id, first.id,
prepared.context.conversationReplayThroughCommentId as string);
preparedContext.conversationReplayThroughCommentId as string);
if (["answered", "new-message"].includes(scenario)) {
expect(replay).toContain("What is the capital of France?");
expect(replay).toContain("Paris.");
@@ -1026,7 +1029,7 @@ describe("chat prompt policy", () => {
it.each([true, false])("preserves rejected-plan changes in task markdown (includeDescription=%s)", (includeDescription) => {
const prompt = buildPaperclipTaskMarkdown({
issue: { id: "chat", title: "Chat", workMode: "planning", conversationAgentId: "agent" },
issue: { id: "chat", identifier: null, title: "Chat", workMode: "planning", conversationAgentId: "agent" },
interaction: { kind: "request_confirmation", status: "rejected" },
planReview: { status: "rejected", reason: "Add CHAT_REVIEW_MARKER and a validation step." },
acceptedPlanContinuation: true,
@@ -1070,7 +1073,7 @@ describe("chat prompt policy", () => {
describe("native conversation finalization", () => {
it("does not require execution completion or schedule a continuation after a successful chat turn", () => {
const decision = {
policyVersion: "paperclip.native-status-arbiter.v1",
policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION,
statusAction: "in_progress",
toStatus: "in_progress",
reasonCode: "completion_evidence_incomplete",
@@ -47,6 +47,7 @@ let currentKeyAgentId = agentId;
let currentAccessCanUser = false;
const mockAgentService = vi.hoisted(() => ({
create: vi.fn(),
getById: vi.fn(),
retryLifecycle: vi.fn(),
pause: vi.fn(),
@@ -12,7 +12,7 @@ import { agentJoinGrantsFromDefaults } from "../services/invite-grants.js";
const support = await getEmbeddedPostgresTestSupport();
const describeDatabase = support.supported ? describe : describe.skip;
const expectedNewAgentGrantKeys: PermissionKey[] = [
const expectedNewAgentGrantKeys: PermissionKey[] = ([
"agents:read",
"agents:configure",
"agents:suggest-changes",
@@ -28,7 +28,7 @@ const expectedNewAgentGrantKeys: PermissionKey[] = [
"tasks:assign",
"tasks:assign_scope",
"tasks:manage_active_checkouts",
].sort();
] satisfies PermissionKey[]).sort();
describeDatabase("new agent configuration defaults", () => {
let db!: ReturnType<typeof createDb>;
@@ -51,6 +51,7 @@ const GROK_PROMPT_OUTPUT = [
const CREDENTIAL_BYTES = '{"tokens":{"access":"SECRET-ACCESS-TOKEN"}}';
const mockAgentService = vi.hoisted(() => ({
create: vi.fn(),
getById: vi.fn(),
getChainOfCommand: vi.fn(async () => []),
}));
@@ -182,7 +182,7 @@ describe("stable live agent directory observation", () => {
traversal: `${reserved}/../../scratch`, "trailing-slash": `${reserved}/scratch/`, nul: `${reserved}/scratch\0`,
}[kind]!;
await expect(prepareSandboxManagedRuntime({
spec: { transport: "sandbox", provider: "fixture", sandboxId: "owned", remoteCwd: root, apiKey: null },
spec: { transport: "sandbox", provider: "fixture", sandboxId: "owned", remoteCwd: root, timeoutMs: 30_000, apiKey: null },
adapterKey: "agent-files", client: {} as never, workspaceLocalDir: root, runtimeRootDir: invalid,
})).rejects.toThrow("Transfer scratch must remain within the lease runtime tree");
expect(fs.existsSync(reserved)).toBe(false);
@@ -908,10 +908,10 @@ describe("persistent agent directories", () => {
it("cleans up a staging failure before any provider starts", async () => {
const runId = randomUUID();
await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId, invocationSource: "on_demand", responsibleUserId: userId });
const shell = vi.spyOn(executionTargetTools, "runAdapterExecutionTargetShellCommand").mockResolvedValue({ exitCode: 1, signal: null, timedOut: false, stdout: "", stderr: "fixture failure" });
const shell = vi.spyOn(executionTargetTools, "runAdapterExecutionTargetShellCommand").mockResolvedValue({ exitCode: 1, signal: null, timedOut: false, stdout: "", stderr: "fixture failure", pid: null, startedAt: null });
try {
await expect(copies.prepare({ ...target(), runId, cwd: home, target: { kind: "remote", transport: "ssh", environmentId: randomUUID(), remoteCwd: "/fixture/task",
spec: { host: "unused.invalid", port: 22, username: "test", remoteCwd: "/fixture/task" } } })).rejects.toThrow("Could not exclude");
spec: { host: "unused.invalid", port: 22, username: "test", remoteCwd: "/fixture/task", remoteWorkspacePath: "/fixture/task", privateKey: null, knownHosts: null, strictHostKeyChecking: true } } })).rejects.toThrow("Could not exclude");
const row = (await copies.get(companyId, runId))!;
expect(row.state).toBe("unavailable");
expect(row.receipt?.baseline).toBeUndefined();
@@ -1424,7 +1424,7 @@ describe("persistent agent directories", () => {
it("stages SSH agent files at the registered root without a nested task workspace", async () => {
const remoteCwd = path.join(home, "ssh-task");
const exclude = vi.spyOn(executionTargetTools, "runAdapterExecutionTargetShellCommand").mockResolvedValue({ exitCode: 0, signal: null, timedOut: false, stdout: "", stderr: "" });
const exclude = vi.spyOn(executionTargetTools, "runAdapterExecutionTargetShellCommand").mockResolvedValue({ exitCode: 0, signal: null, timedOut: false, stdout: "", stderr: "", pid: null, startedAt: null });
const stage = vi.spyOn(ssh, "syncDirectoryToSsh").mockImplementation(async input => {
await fs.mkdir(path.dirname(input.remoteDir), { recursive: true });
await fs.cp(input.localDir, input.remoteDir, { recursive: true });
@@ -1436,7 +1436,7 @@ describe("persistent agent directories", () => {
const runId = randomUUID();
await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId, invocationSource: "on_demand", responsibleUserId: userId });
const target = { kind: "remote" as const, transport: "ssh" as const, environmentId: randomUUID(), remoteCwd,
spec: { host: "unused.invalid", port: 22, username: "test", remoteCwd } };
spec: { host: "unused.invalid", port: 22, username: "test", remoteCwd, remoteWorkspacePath: remoteCwd, privateKey: null, knownHosts: null, strictHostKeyChecking: true } };
await db.insert(environments).values({ id: target.environmentId, name: "Fixture SSH", driver: "ssh" });
// Legacy no-ID receipt: only the unique run/environment lease authorizes retrieval.
await db.insert(environmentLeases).values({ companyId, environmentId: target.environmentId, heartbeatRunId: runId, provider: "ssh", providerLeaseId: "fixture-host" });
@@ -1606,8 +1606,8 @@ describe("persistent agent directories", () => {
const { copy, lease } = await unavailableRemote({ state: "destroyed" });
const remoteCwd = "/fixture/cleanup-task";
const executionTarget = { kind: "remote" as const, transport: "ssh" as const, environmentId: lease.environmentId, leaseId: lease.id, remoteCwd,
spec: { host: "unused.invalid", port: 22, username: "test", remoteCwd } };
const shell = vi.spyOn(executionTargetTools, "runAdapterExecutionTargetShellCommand").mockResolvedValue({ exitCode: 0, signal: null, timedOut: false, stdout: "", stderr: "" });
spec: { host: "unused.invalid", port: 22, username: "test", remoteCwd, remoteWorkspacePath: remoteCwd, privateKey: null, knownHosts: null, strictHostKeyChecking: true } };
const shell = vi.spyOn(executionTargetTools, "runAdapterExecutionTargetShellCommand").mockResolvedValue({ exitCode: 0, signal: null, timedOut: false, stdout: "", stderr: "", pid: null, startedAt: null });
const stage = vi.spyOn(ssh, "syncDirectoryToSsh").mockResolvedValue(undefined);
let stopCommitted!: () => void, finishCleanup!: () => void;
const committed = new Promise<void>(resolve => { stopCommitted = resolve; });
@@ -1728,7 +1728,7 @@ describe("persistent agent directories", () => {
await db.insert(environmentLeases).values(lease);
const execute = vi.fn(), restoreWorkspace = vi.fn(), cleanupWorkspaceSnapshot = vi.fn().mockResolvedValue(undefined);
const executionTarget = { kind: "remote" as const, transport: "sandbox" as const, environmentId, leaseId, remoteCwd, runner: { execute } };
const shell = vi.spyOn(executionTargetTools, "runAdapterExecutionTargetShellCommand").mockResolvedValue({ exitCode: 0, signal: null, timedOut: false, stdout: "", stderr: "" });
const shell = vi.spyOn(executionTargetTools, "runAdapterExecutionTargetShellCommand").mockResolvedValue({ exitCode: 0, signal: null, timedOut: false, stdout: "", stderr: "", pid: null, startedAt: null });
const stage = vi.spyOn(executionTargetTools, "prepareAdapterExecutionTargetRuntime").mockImplementation(async options => ({ target: executionTarget,
workspaceRemoteDir: path.posix.join(remoteCwd, ".paperclip-runtime", "agent-files", agentId, runId), runtimeRootDir: options.runtimeRootDir ?? null,
assetDirs: {}, additionalSourceDirs: {}, additionalSourceFailures: [], workspaceSyncSnapshot: null, restoreWorkspace, cleanupWorkspaceSnapshot }));
@@ -94,7 +94,7 @@ describe("incremental managed file checkpoints", () => {
if (first) { first = false; await fs.appendFile(filename, Buffer.alloc(1024 * 1024, 2)); }
yield chunk;
}
})()) as typeof handle.createReadStream;
})()) as unknown as typeof handle.createReadStream;
}
return handle;
});
@@ -297,7 +297,7 @@ describe("agent-created hires use managed AI connections", () => {
const otherOwner = `other-${f.companyId}`;
await db.insert(companyMemberships).values({ companyId: f.companyId, principalType: "user", principalId: otherOwner, membershipRole: "member", status: "active" });
const account = await aiConnectionService(db).save(f.companyId, otherOwner, { provider: "openai", method: "api_key", ownership: "shared", name: "Shared credential, restricted installation", apiKey: "fixture", agentIds: [f.agentId], allAgents: false }, "fixture");
await toolAccessService(db).replaceConnectionGrantMembers(account.connectionId, account.grantId, [f.userId], { userId: otherOwner });
await toolAccessService(db).replaceConnectionGrantMembers(account.connectionId, account.grantId, [f.userId], { actorType: "user", actorId: otherOwner });
const member: AiConnectionPoolMember = { id: randomUUID(), binding: { ...account, provider: "openai", method: "api_key", mode: "shared" }, profile: { provider: "codex", model: "gpt-5.6-sol" } };
const p = await poolFixture(f, [member]);
const agent = hired(await request(f.app).post(`/api/companies/${f.companyId}/agents`).send({ name: "Restricted pool teammate", role: "engineer", adapterType: f.adapterType, runtimeConfig: { aiConnection: p.binding } }));
+1 -1
View File
@@ -129,7 +129,7 @@ describe("agent cryptographic identity", () => {
await execute({
runId: randomUUID(), agent, agentIdentity: identity,
config: { command: process.execPath, cwd: directory, args: ["-e", `const {sign}=require('node:crypto'); process.stdout.write(sign(null,Buffer.from(process.env.CHALLENGE,'base64'),process.env.PAPERCLIP_AGENT_PRIVATE_KEY).toString('base64'));`], env: { CHALLENGE: challenge, PAPERCLIP_AGENT_PRIVATE_KEY: "configured override" } },
context: {}, runtime: { sessionId: null, sessionParams: null, taskKey: null },
context: {}, runtime: { sessionId: null, sessionParams: null, sessionDisplayId: null, taskKey: null },
onLog: async (_stream, chunk) => { logs.push(chunk); }, onMeta: async meta => { metas.push(meta); },
});
expect(verify(null, Buffer.from(challenge, "base64"), publicPem, Buffer.from(logs.join(""), "base64"))).toBe(true);
@@ -43,7 +43,7 @@ describe.skipIf(!support.supported)("agent-scoped runner qualification", () => {
await db.insert(authUsers).values({ id: userId, name: "Company owner", email: `${userId}@example.test`, emailVerified: true, createdAt: new Date(), updatedAt: new Date() });
await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: userId, membershipRole: "owner", status: "active" });
await db.insert(principalPermissionGrants).values(grantsForHumanRole("owner").map(grant => ({ ...grant, companyId, principalType: "user" as const, principalId: userId })));
await db.insert(environments).values({ id: environmentId, name: `Paperclip Computer ${environmentId}`, driver: "sandbox", status: "active", config: { provider: "daytona", snapshot: "managed-snapshot" } });
await db.insert(environments).values({ id: environmentId, name: `Paperclip Computer ${environmentId}`, driver: "sandbox", status: "active", config: { provider: "daytona", snapshot: "managed-snapshot", reuseLease: false } });
await db.insert(agents).values({ id: agentId, companyId, name: "Amy fixture", adapterType: "claude_local", defaultEnvironmentId: environmentId,
adapterConfig: { model: "claude-opus-5-5", effort: "high", maxTurnsPerRun: 1000, dangerouslySkipPermissions: true } });
app = express(); app.use(express.json());
@@ -89,7 +89,7 @@ describe.skipIf(!support.supported)("agent-scoped runner qualification", () => {
it("reuses a current native snapshot proof without invalidating it or allocating another probe", async () => {
await db.update(agents).set({ adapterType: "paperclip_runner", adapterConfig: { provider: "opencode" } }).where(eq(agents.id, agentId));
const config = { provider: "daytona", snapshot: "managed-snapshot" };
const config = { provider: "daytona", snapshot: "managed-snapshot", reuseLease: false };
const fingerprint = fingerprintEnvironmentSandboxProviderConfig(config);
const saved = (await environmentService(db).getById(environmentId))!;
const runtime = await resolveEnvironmentDriverConfigForRuntime(db, companyId, saved, { applyCustomImageTemplate: true, readOnlyCustomImageTemplate: true });
@@ -98,14 +98,14 @@ describeDatabase("connection assignment synchronization", () => {
const [project] = await db.insert(projects).values({ companyId, name: "Project binding fixture" }).returning();
for (let round = 0; round < 2; round++) {
let before = (await events()).length;
await service.bindProfile(profile.id, { targetType: "agent", targetId: agentId });
await service.bindProfile(profile.id, { targetType: "agent", targetId: agentId, priority: 100 });
expect((await events()).slice(before).map(event => event.resourceId)).toEqual([agentId]);
before = (await events()).length;
await service.unbindProfile(profile.id, { targetType: "agent", targetId: agentId });
expect((await events()).slice(before).map(event => event.resourceId)).toEqual([agentId]);
}
const beforeProject = await events();
await service.bindProfile(profile.id, { targetType: "project", targetId: project.id });
await service.bindProfile(profile.id, { targetType: "project", targetId: project.id, priority: 100 });
await service.unbindProfile(profile.id, { targetType: "project", targetId: project.id });
expect(await events()).toEqual(beforeProject);
let before = (await events()).length;
@@ -121,7 +121,7 @@ describeDatabase("connection assignment synchronization", () => {
await db.update(toolConnectionInstalls).set({ targetId: peerId }).where(eq(toolConnectionInstalls.id, install.id));
expect((await events()).slice(before).map(event => event.resourceId).sort()).toEqual([agentId, peerId].sort());
before = (await events()).length;
await service.bindProfile(profile.id, { targetType: "company", targetId: companyId });
await service.bindProfile(profile.id, { targetType: "company", targetId: companyId, priority: 100 });
expect((await events()).slice(before).map(event => event.resourceId).sort()).toEqual([agentId, peerId].sort());
});
it("keeps connector changes and their events atomic and company-scoped", async () => {
@@ -217,9 +217,9 @@ describeDatabase("connection assignment synchronization", () => {
const single = createDb(database.connectionString, { maxConnections: 1 });
const service = toolAccessService(single);
let timer: ReturnType<typeof setTimeout> | undefined;
const work = change === "bind" ? service.bindProfile(source.id, { targetType: "agent", targetId: agentId })
const work = change === "bind" ? service.bindProfile(source.id, { targetType: "agent", targetId: agentId, priority: 100 })
: change === "unbind" ? service.unbindProfile(source.id, { targetType: "agent", targetId: agentId })
: service.deleteProfile(source.id, change === "reassign" ? { reassignToProfileId: target.id } : { force: true });
: service.deleteProfile(source.id, change === "reassign" ? { force: false, reassignToProfileId: target.id } : { force: true });
const settled = Promise.allSettled([work]);
try {
await Promise.race([work, new Promise<never>((_, reject) => { timer = setTimeout(() => reject(new Error("Profile operation waited for another pooled connection")), 3000); })]);
@@ -261,9 +261,9 @@ describeDatabase("connection assignment synchronization", () => {
expect(activity.wait_event_type).toBe("Lock");
}, { timeout: 5000 });
const mutation = run(writer => change === "bind"
? writer.bindProfile(profile.id, { targetType: "agent", targetId: targetAgent })
? writer.bindProfile(profile.id, { targetType: "agent", targetId: targetAgent, priority: 100 })
: change === "unbind" ? writer.unbindProfile(profile.id, { targetType: "agent", targetId: targetAgent })
: writer.deleteProfile(source!.id, { reassignToProfileId: profile.id }));
: writer.deleteProfile(source!.id, { force: false, reassignToProfileId: profile.id }));
const mutationResult = Promise.allSettled([mutation]);
let finishResult: Promise<PromiseSettledResult<unknown>[]> | undefined;
try {
@@ -324,7 +324,7 @@ describeDatabase("connection assignment synchronization", () => {
const firstResult = Promise.allSettled([first]);
try {
await waitForLock(0);
second = Promise.allSettled([run(change === "connection settings" ? adopt : client => toolAccessService(client).bindProfile(bindingProfile!.id, { targetType: "agent", targetId: lower }, { actorType: "agent", actorId: higher }))]);
second = Promise.allSettled([run(change === "connection settings" ? adopt : client => toolAccessService(client).bindProfile(bindingProfile!.id, { targetType: "agent", targetId: lower, priority: 100 }, { actorType: "agent", actorId: higher }))]);
await waitForLock(1);
} finally { release(); await blocker; }
for (const result of [...await firstResult, ...await second!]) if (result.status === "rejected") throw result.reason;
@@ -373,7 +373,7 @@ describeDatabase("connection assignment synchronization", () => {
await waitForLock(0);
replacement = Promise.allSettled([run(service => change === "installs"
? service.putConnectionInstalls(connection.id, { installs: [{ targetType: "agent", targetId: lower }] })
: change === "reassign" ? service.deleteProfile(source.id, { reassignToProfileId: target.id })
: change === "reassign" ? service.deleteProfile(source.id, { force: false, reassignToProfileId: target.id })
: service.finishGalleryAppConnection(companyId, connection.id, { enabledCatalogEntryIds: [], askFirstCatalogEntryIds: [], access: { agentIds: [change === "finish" ? lower : higher] } }))]);
await waitForLock(1);
} finally { release(); await blocker; }
@@ -72,7 +72,7 @@ describeDatabase("connector access runtime synchronization", () => {
let memberId: string | undefined;
if (change === "uninstall") {
await toolAccessService(db).putConnectionInstalls(connectionId, { installs: [{ targetType: "agent", targetId: agentId }] });
expect((await toolAccessService(db).getConnection(connectionId, companyId)).config.mcpAgentAccessConfigured).toBe(true);
expect((await toolAccessService(db).getConnection(connectionId, companyId)).config?.mcpAgentAccessConfigured).toBe(true);
first = client => toolAccessService(client).putConnectionInstalls(connectionId, { installs: [] });
second = client => toolAccessService(client).finishGalleryAppConnection(companyId, connectionId, { enabledCatalogEntryIds: [], askFirstCatalogEntryIds: [], access: "all_agents" });
} else {
@@ -80,7 +80,7 @@ describeDatabase("connector access runtime synchronization", () => {
memberId = member.id;
await db.insert(principalPermissionGrants).values({ companyId, principalType: "user", principalId: "alice", permissionKey: "agents:create" });
first = client => accessService(client).updateMemberAndPermissions(companyId, member.id, { membershipRole: "admin", grants: [{ permissionKey: "agents:create" }] }, null);
second = client => accessService(client).setMemberPermissions(companyId, member.id, [{ permissionKey: "issues:create" }], null);
second = client => accessService(client).setMemberPermissions(companyId, member.id, [{ permissionKey: "tasks:assign" }], null);
}
let locked!: () => void; let release!: () => void;
const held = new Promise<void>(resolve => { locked = resolve; });
@@ -118,7 +118,7 @@ describeDatabase("connector access runtime synchronization", () => {
expect(bindings.some(binding => binding.metadata?.source === "tool_connection_install")).toBe(false);
} else {
expect(await accessService(db).getMemberById(companyId, memberId!)).toMatchObject({ membershipRole: "admin" });
expect(await db.select().from(principalPermissionGrants).where(eq(principalPermissionGrants.companyId, companyId))).toEqual([expect.objectContaining({ permissionKey: "issues:create" })]);
expect(await db.select().from(principalPermissionGrants).where(eq(principalPermissionGrants.companyId, companyId))).toEqual([expect.objectContaining({ permissionKey: "tasks:assign" })]);
}
}, 15000);
it.each(["email allowAgent", "connection intent"] as const)("orders outer %s with direct install saves", async caller => {
@@ -132,7 +132,7 @@ describeDatabase("connector access runtime synchronization", () => {
await db.insert(companyMemberships).values({ companyId, principalType: "user", principalId: userId, status: "active", membershipRole: "member" });
await db.insert(issues).values({ id: issueId, companyId, title: "Use Notion", status: "in_progress", assigneeAgentId: agentId });
await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId, status: "running", responsibleUserId: userId, contextSnapshot: { issueId } });
const pending = await connectionIntentService(db).request({ sub: agentId, company_id: companyId, run_id: runId, responsible_user_id: userId, scope: "connection_intents", iat: 1, exp: 2, instance_id: "test" }, "notion");
const pending = await connectionIntentService(db).request({ sub: agentId, company_id: companyId, run_id: runId, responsible_user_id: userId }, "notion");
expect(pending.interactionId).toEqual(expect.any(String));
await db.update(toolConnections).set({ healthStatus: "ok", config: { sourceTemplateKey: "notion", mcpAgentAccessConfigured: true } }).where(eq(toolConnections.id, connectionId));
await db.insert(connectionGrants).values({ companyId, connectionId, kind: "organization", status: "active", isDefault: true });
@@ -316,7 +316,7 @@ describeDatabase("connector access runtime synchronization", () => {
});
it("limits delegation creation to its target without waiting on unrelated agents", async () => {
await db.insert(companyMemberships).values(["alice", "bob"].map(principalId => ({ companyId, principalType: "user", principalId, status: "active" })));
const grants = await db.insert(connectionGrants).values(["alice", "bob"].map(subjectUserId => ({ companyId, connectionId, kind: "user", subjectUserId }))).returning();
const grants = await db.insert(connectionGrants).values(["alice", "bob"].map(subjectUserId => ({ companyId, connectionId, kind: "user" as const, subjectUserId }))).returning();
const baseline = await events();
const [lower, higher] = [agentId, peerId].sort();
let locked!: () => void; let release!: () => void;
@@ -136,7 +136,7 @@ describeDatabase("tool definition runtime synchronization", () => {
await db.update(toolConnections).set({ config: { mcpAgentAccessConfigured: true } }).where(eq(toolConnections.id, connectionId));
await db.insert(toolProfiles).values({ companyId, profileKey: `app:${connectionId}`, name: "Install profile" });
const [lower, higher] = [randomUUID(), randomUUID()].sort();
await db.insert(toolPolicies).values([higher, lower].map((id, index) => ({ id, companyId, name: `Managed policy ${index}`, policyType: "require_approval", priority: (index + 1) * 100, config: { source: "app_gallery_finish", connectionId } })));
await db.insert(toolPolicies).values([higher, lower].map((id, index) => ({ id, companyId, name: `Managed policy ${index}`, policyType: "require_approval" as const, priority: (index + 1) * 100, config: { source: "app_gallery_finish", connectionId } })));
let locked!: () => void; let release!: () => void;
const held = new Promise<void>(resolve => { locked = resolve; });
const resume = new Promise<void>(resolve => { release = resolve; });
@@ -222,10 +222,10 @@ describeDatabase("tool definition runtime synchronization", () => {
expect(row.wait_event_type).toBe("Lock");
}, { timeout: 5000 });
const mutation = run(writer => change === "profile entry add"
? writer.addProfileEntry(profile.id, { selectorType: "catalog_entry", catalogEntryId: entry.id, connectionId, applicationId })
? writer.addProfileEntry(profile.id, { selectorType: "catalog_entry", effect: "include", catalogEntryId: entry.id, connectionId, applicationId })
: change === "profile entry edit" ? writer.updateProfileEntry(profileEntry.id, { effect: "exclude" })
: change === "profile entry delete" ? writer.deleteProfileEntry(profileEntry.id)
: writer.updateProfile(profile.id, { entries: [{ selectorType: "catalog_entry", catalogEntryId: entry.id, connectionId, applicationId }] }));
: writer.updateProfile(profile.id, { entries: [{ selectorType: "catalog_entry", effect: "include", catalogEntryId: entry.id, connectionId, applicationId }] }));
const mutationResult = Promise.allSettled([mutation]);
let finishResult: Promise<PromiseSettledResult<unknown>[]> | undefined;
try {
@@ -279,7 +279,7 @@ describeDatabase("tool definition runtime synchronization", () => {
it.each(["add", "edit", "delete", "replace"] as const)("completes profile entry %s with a single database connection", async change => {
const [profile] = await db.insert(toolProfiles).values({ companyId, profileKey: "single-entry", name: "Single entry" }).returning();
const input = { selectorType: "connection" as const, applicationId, connectionId };
const input = { selectorType: "connection" as const, effect: "include" as const, applicationId, connectionId };
const [entry] = await db.insert(toolProfileEntries).values({ companyId, profileId: profile.id, ...input }).returning();
const single = createDb(database.connectionString, { maxConnections: 1 });
const service = toolAccessService(single);
@@ -301,7 +301,7 @@ describeDatabase("tool definition runtime synchronization", () => {
const [profile] = await db.insert(toolProfiles).values({ companyId, profileKey: "scoped", name: "Scoped" }).returning();
await db.insert(toolProfileBindings).values({ companyId, profileId: profile.id, targetType: "agent", targetId: agentId });
const [entry] = await db.insert(toolProfileEntries).values({ companyId, profileId: profile.id, selectorType: "connection", connectionId }).returning();
const [policy] = await db.insert(toolPolicies).values({ companyId, name: "Scoped", policyType: "deny", selectors: { agentId } }).returning();
const [policy] = await db.insert(toolPolicies).values({ companyId, name: "Scoped", policyType: "block", selectors: { agentId } }).returning();
for (let index = 0; index < 4; index++) {
const before = (await events()).length;
if (change === "entry") await db.update(toolProfileEntries).set({ effect: index % 2 ? "include" : "exclude" }).where(eq(toolProfileEntries.id, entry.id));
@@ -318,7 +318,7 @@ describeDatabase("tool definition runtime synchronization", () => {
});
it("uses old and new policy selectors with the execution match semantics", async () => {
const [policy] = await db.insert(toolPolicies).values({ companyId, name: "Retarget", policyType: "deny", selectors: { agentId } }).returning();
const [policy] = await db.insert(toolPolicies).values({ companyId, name: "Retarget", policyType: "block", selectors: { agentId } }).returning();
const write = async (selectors: Record<string, unknown>, expected: string[]) => {
const before = (await events()).length;
await db.update(toolPolicies).set({ selectors }).where(eq(toolPolicies.id, policy.id));
@@ -365,7 +365,7 @@ describeDatabase("tool definition runtime synchronization", () => {
const [profile] = await db.insert(toolProfiles).values({ companyId, profileKey: "coalesced", name: "Coalesced" }).returning();
await db.insert(toolProfileBindings).values({ companyId, profileId: profile.id, targetType: "agent", targetId: agentId });
const [entry] = await db.insert(toolProfileEntries).values({ companyId, profileId: profile.id, selectorType: "connection", connectionId }).returning();
const [policy] = await db.insert(toolPolicies).values({ companyId, name: "Coalesced", policyType: "deny", selectors: { agentId } }).returning();
const [policy] = await db.insert(toolPolicies).values({ companyId, name: "Coalesced", policyType: "block", selectors: { agentId } }).returning();
let before = (await events()).length;
await db.transaction(async tx => {
await tx.update(toolProfileEntries).set({ effect: "exclude" }).where(eq(toolProfileEntries.id, entry.id));
@@ -383,7 +383,7 @@ describeDatabase("tool definition runtime synchronization", () => {
it("orders scoped policy reorder before company agent fanout", async () => {
const [lower, higher] = [agentId, peerId].sort();
const policies = await db.insert(toolPolicies).values([higher, lower].map((target, index) => ({ companyId, name: `Scoped ${index}`, policyType: "deny", priority: (index + 5) * 100, selectors: { agentId: target } }))).returning();
const policies = await db.insert(toolPolicies).values([higher, lower].map((target, index) => ({ companyId, name: `Scoped ${index}`, policyType: "block" as const, priority: (index + 5) * 100, selectors: { agentId: target } }))).returning();
let locked!: () => void; let release!: () => void;
const held = new Promise<void>(resolve => { locked = resolve; });
const resume = new Promise<void>(resolve => { release = resolve; });
@@ -454,7 +454,7 @@ describeDatabase("tool definition runtime synchronization", () => {
}, 15000);
it("retries changed old references before taking profile runtime locks", async () => {
const [oldApp, newApp] = await db.insert(toolApplications).values(["Old retry app", "New retry app"].map(name => ({ companyId, name, type: "mcp_http" }))).returning();
const [oldApp, newApp] = await db.insert(toolApplications).values(["Old retry app", "New retry app"].map(name => ({ companyId, name, type: "mcp_http" as const }))).returning();
const [profile] = await db.insert(toolProfiles).values({ companyId, profileKey: "retry-references", name: "Retry references" }).returning();
await db.insert(toolProfileBindings).values({ companyId, profileId: profile.id, targetType: "agent", targetId: agentId });
const [entry] = await db.insert(toolProfileEntries).values({ companyId, profileId: profile.id, selectorType: "application", applicationId: oldApp.id }).returning();
@@ -160,7 +160,7 @@ describeEmbeddedPostgres("agent secret routes", () => {
targetType: "agent",
targetId: fixture.agentId,
configPath: "access.API_ONLY_KEY",
projectionClass: "class_2_runtime_only",
projectionClass: "unclassified",
});
const projectBinding = await svc.createBinding({
companyId: fixture.companyId,
@@ -184,7 +184,7 @@ describeEmbeddedPostgres("agent secret routes", () => {
const list = await request(createApp(fixture)).get("/api/agents/me/secrets");
expect(list.status).toBe(200);
expect(list.body.secrets).toEqual([
expect.objectContaining({ key: "api_only_key", delivery: "api", projectionClass: "class_2_runtime_only" }),
expect.objectContaining({ key: "api_only_key", delivery: "api", projectionClass: "unclassified" }),
expect.objectContaining({ key: "env_only_key", delivery: "env" }),
expect.objectContaining({ key: "project_key", delivery: "env" }),
]);
@@ -1,6 +1,7 @@
import express from "express";
import request from "supertest";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { AdapterEnvironmentTestContext } from "@paperclipai/adapter-utils";
import type { ServerAdapterModule } from "../adapters/index.js";
const mockAgentService = vi.hoisted(() => ({
@@ -837,7 +838,7 @@ describe("agent test-environment route", () => {
const { registerServerAdapter, getServerAdapter, unregisterServerAdapter } = await import("../adapters/index.js");
const previous = getServerAdapter("claude_local");
unregisterServerAdapter("claude_local");
const cliProbeSpy = vi.fn(async () => ({
const cliProbeSpy = vi.fn(async (_context: AdapterEnvironmentTestContext) => ({
adapterType: "claude_local",
status: "fail" as const,
checks: [{ code: "adapter_command_missing", level: "error" as const, message: 'Command not found in PATH: "claude"' }],
@@ -57,7 +57,7 @@ const mockInstanceSettingsService = vi.hoisted(() => ({
getGeneral: vi.fn(async () => ({ censorUsernameInLogs: false })),
getExperimental: vi.fn(async () => ({ enableManagedSandboxOnly: false })),
}));
const mockRunClaudeLogin = vi.hoisted(() => vi.fn(async () => ({ ok: true })));
const mockRunClaudeLogin = vi.hoisted(() => vi.fn(async (_input: { config: { env: Record<string, string> } }) => ({ ok: true })));
vi.mock("../services/index.js", () => ({
agentService: () => mockAgentService,
@@ -1103,7 +1103,6 @@ describeEmbeddedPostgres("agent service Claude OAuth binding claim", () => {
companyId: scope.companyId,
ownerUserId: scope.ownerUserId,
adapterType: "claude_local",
environmentId: scope.environmentId,
});
const writer = createSetupTokenSecretWriter({ db });
@@ -32,7 +32,7 @@ if (!support.supported) console.warn(`Managed-account database tests unavailable
const [connection] = await db.insert(toolConnections).values({ companyId: company.id, applicationId: application.id, uid: randomUUID(), name: `${provider} gateway`, transport: "mcp_remote", authKind: "none", credentialPolicy: "shared", status: "active", enabled: true, config: { sourceTemplateKey: provider, url: provider === "arcade" ? "https://api.arcade.dev/mcp/test" : "https://executor.example/mcp", managementUrl: "https://executor.example/team/integrations" } }).returning();
await db.insert(connectionGrants).values({ companyId: company.id, connectionId: connection.id, kind: "organization", status: "active", isDefault: true });
const names = provider === "executor" ? ["integrations"] : ["Notion.ListPages"];
await db.insert(toolCatalogEntries).values(names.map(toolName => ({ companyId: company.id, connectionId: connection.id, name: toolName, toolName, versionHash: "v1", status: "active", riskLevel: "read" as const })));
await db.insert(toolCatalogEntries).values(names.map(toolName => ({ companyId: company.id, connectionId: connection.id, name: toolName, toolName, versionHash: "v1", status: "active" as const, riskLevel: "read" as const })));
let accounts = [{ integration: "notion", owner: "user", connection: "Work", lastHealth: { status: "healthy", checkedAt: Date.now() } }];
let fail = false;
let partial = false;
@@ -88,7 +88,7 @@ if (!support.supported) console.warn(`Managed-account database tests unavailable
expect((await f.access.listAggregatorApps(f.connection.id, f.actor)).apps).toHaveLength(0);
});
it("reports unsupported Executor inventory without trying arbitrary tools", async () => {
const f = await fixture(); await sync(f); f.calls.length = 0; await db.update(toolCatalogEntries).set({ status: "inactive" }).where(eq(toolCatalogEntries.connectionId, f.connection.id));
const f = await fixture(); await sync(f); f.calls.length = 0; await db.update(toolCatalogEntries).set({ status: "disabled" }).where(eq(toolCatalogEntries.connectionId, f.connection.id));
expect((await f.access.syncAggregatorApps(f.connection.id, true, f.actor)).discovery.availability).toBe("unsupported");
expect(f.calls).toHaveLength(0);
expect((await f.access.listAggregatorApps(f.connection.id, f.actor)).apps[0].freshness).toBe("stale");
+3 -3
View File
@@ -24,12 +24,12 @@ describe("personal AI credential preflight recovery", () => {
expect(isAiAuthenticationBlocked({ ...run, livenessState: "blocked" })).toBe(true);
});
it.each([
it.each<[string, unknown]>([
["tool credential", [{ ...missingClaudeKey, envKey: "GITHUB_TOKEN", configPath: "env.GITHUB_TOKEN" }]],
["company secret", [{ ...missingClaudeKey, bindingType: "secret_ref" }]],
["unknown error", [{ ...missingClaudeKey, errorCode: "user_secret_definition_missing" }]],
["adapter field", [{ ...missingClaudeKey, configPath: "apiKey" }]],
...["project", "environment", "routine", "plugin"].map(consumerType => [
...["project", "environment", "routine", "plugin"].map((consumerType): [string, unknown] => [
`${consumerType} binding`, [{ ...missingClaudeKey, consumerType }],
]),
["different agent", [{ ...missingClaudeKey, consumerId: "another-agent" }]],
@@ -44,7 +44,7 @@ describe("personal AI credential preflight recovery", () => {
});
it("only classifies a failed preflight as an inline wait after the card is persisted", () => {
const input = { ...failure([missingClaudeKey]), runStatus: "failed", issue: { status: "blocked" },
const input = { ...failure([missingClaudeKey]), runStatus: "failed", issue: { status: "blocked", title: "Blocked task", description: null },
stdoutExcerpt: null, stderrExcerpt: null, error: null, continuationAttempt: 0, evidence: null };
expect(classifyRunLiveness(input).livenessState).toBe("failed");
expect(classifyRunLiveness({ ...input, authenticationRepairRequested: true })).toMatchObject({
+16 -16
View File
@@ -136,7 +136,7 @@ describe("managed AI connections", () => {
displayName: "Bootstrap", description: "Bootstrap fixture", author: "Test", categories: ["automation"],
capabilities: ["environment.drivers.register"], entrypoints: { worker: "worker.js" },
environmentDrivers: [{ driverKey: "tasks", displayName: "Tasks", supportsTasks: true, configSchema: { type: "object" } }] } });
const [environment] = await db.insert(environments).values({ companyId, name: "Agent computer", driver: "plugin",
const [environment] = await db.insert(environments).values({ name: "Agent computer", driver: "plugin",
config: { pluginKey, driverKey: "tasks", driverConfig: {} } }).returning();
const token = "github_pat_fixture_plugin_bootstrap";
const account = await service.save(companyId, owner, { provider: "github", method: "api_key", ownership: "personal", name: "Bootstrap", apiKey: token, agentIds: [], allAgents: false }, token);
@@ -300,7 +300,7 @@ describe("managed AI connections", () => {
await db.insert(principalPermissionGrants).values([owner, audience].map(principalId => ({ companyId, principalId, principalType: "user", permissionKey: "agents:create" })));
const token = "github_pat_fixture_shared_discovery";
const account = await service.save(companyId, owner, { provider: "github", method: "api_key", ownership: "shared", name: "Shared discovery", apiKey: token, agentIds: [], allAgents: false }, token);
await db.insert(connectionGrantMembers).values([owner, audience].map(subjectId => ({ companyId, grantId: account.grantId, subjectType: "user", subjectId })));
await db.insert(connectionGrantMembers).values([owner, audience].map(subjectId => ({ companyId, grantId: account.grantId, subjectType: "user" as const, subjectId })));
const binding = { provider: "github", method: "api_key", mode: "shared", ...account };
const probe = vi.spyOn(copilotProbe, "probeCopilotConnection").mockResolvedValue({ status: "verified", version: "1.0.88", profileDigest: "fixture-profile", promptSent: false, models: [{ id: "gpt-5.6-luna", label: "GPT" }] });
const app = express(); app.use(express.json());
@@ -1338,7 +1338,7 @@ describe("managed AI connections", () => {
agentIds: [], allAgents: true,
routing: { kind: "local", protocol, baseUrl: "http://127.0.0.1:9000/v1", auth: "none", models: [] },
}, "");
const binding = { provider, method: "api_key", mode: "connection", connectionId: account.connectionId, grantId: account.grantId } as const;
const binding = { provider, method: "api_key", mode: "delegated", connectionId: account.connectionId, grantId: account.grantId } as const;
const config = { model: "fixture-model", env: { OPENAI_API_KEY: "host-key", ANTHROPIC_API_KEY: "host-key" } };
const first = await prepareManagedAiRuntime(db, { ...input, adapterType, responsibleUserId: "alice", binding, config });
const second = await prepareManagedAiRuntime(db, { ...input, adapterType, responsibleUserId: "alice", binding, config });
@@ -1357,7 +1357,7 @@ describe("managed AI connections", () => {
}, "fixture-grok-key");
const taskKey = `grok-history-${randomUUID()}`;
const runInput = { ...input, taskKey, adapterType: "grok_local", responsibleUserId: "alice", config: {},
binding: { provider: "xai", method: "api_key", mode: "connection", connectionId: account.connectionId, grantId: account.grantId } as const };
binding: { provider: "xai", method: "api_key", mode: "delegated", connectionId: account.connectionId, grantId: account.grantId } as const };
const first = await prepareManagedAiRuntime(db, runInput);
const scope = createHash("sha256").update(JSON.stringify([
companyId, agentId, taskKey, first.sessionIdentity,
@@ -1597,12 +1597,12 @@ console.log(JSON.stringify({ type: "result", subtype: "success", result: "hello"
const account = await service.save(companyId, "alice", { provider: "anthropic", method: "subscription", ownership: "shared", name: "Shared usage account", loginSessionId: "fixture", agentIds: [], allAgents: true }, "shared-usage-secret");
const selected = { provider: "anthropic", method: "subscription", mode: "shared", ...account } as const;
const tools = toolAccessService(db);
await tools.replaceConnectionGrantMembers(account.connectionId, account.grantId, ["alice"], { userId: "alice" });
await tools.replaceConnectionGrantMembers(account.connectionId, account.grantId, ["alice"], { actorType: "user", actorId: "alice" });
const fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response(JSON.stringify({ five_hour: { utilization: 100 }, extra_usage: { is_enabled: false } })));
try {
await expect(service.probeUsage(companyId, "bob", account.connectionId)).rejects.toThrow("not found");
expect(fetchSpy).not.toHaveBeenCalled();
await tools.replaceConnectionGrantMembers(account.connectionId, account.grantId, ["bob"], { userId: "alice" });
await tools.replaceConnectionGrantMembers(account.connectionId, account.grantId, ["bob"], { actorType: "user", actorId: "alice" });
for (const config of [{ adapterType: "claude_local" }, { adapterType: "paperclip_runner", runnerProvider: "acpx", acpxAgent: "claude" }]) {
const row = await service.select({ companyId, userId: "bob", agentId, binding: selected, ...config });
expect(await service.probeUsage(companyId, "bob", row.connection.id, row.grant.id)).toMatchObject({ status: "ok", overage: { available: false }, limits: [{ limitReached: true }, { allowed: false }] });
@@ -1673,7 +1673,7 @@ console.log(JSON.stringify({ type: "result", subtype: "success", result: "hello"
it("reconnects JSONB routing without changing its identity or access", async () => {
const routing = { kind: "gateway", protocol: "responses", auth: "bearer", baseUrl: "https://gateway.example/v1", models: [{ id: "gateway-model" }] } as const;
const input = { provider: "openai", method: "api_key", name: "Reconnect gateway", ownership: "personal", allAgents: false, agentIds: [agentId], routing: { ...routing, models: [...routing.models] } } as const;
const saved = await service.save(companyId, "alice", { ...input, agentIds: [...input.agentIds] }, "old-gateway-credential");
const saved = await service.save(companyId, "alice", { ...input, agentIds: [...input.agentIds], routing: { ...routing, models: [...routing.models] } }, "old-gateway-credential");
const [stored] = await db.select().from(toolConnections).where(eq(toolConnections.id, saved.connectionId));
expect(stored!.config.ai).toMatchObject({ routing });
expect(stored!.config.sourceTemplateKey).toBe("responses-api");
@@ -1788,7 +1788,7 @@ console.log(JSON.stringify({ type: "result", subtype: "success", result: "hello"
const runId = randomUUID();
const vault = secretService(db);
const definition = await vault.createUserSecretDefinition(companyId, {
key: `ANTHROPIC_API_KEY.${randomUUID()}`, name: "ANTHROPIC_API_KEY for onboarding",
key: `ANTHROPIC_API_KEY.${randomUUID()}`, name: "ANTHROPIC_API_KEY for onboarding", provider: "local_encrypted",
}, { userId: "alice" });
const ownerSecret = await vault.createCurrentUserSecretValue(companyId, "alice", {
definitionId: definition.id, value: "fixture-original-owner-key",
@@ -1877,8 +1877,8 @@ console.log(JSON.stringify({ type: "result", subtype: "success", result: "hello"
}
expect(subEnv.HOME).not.toBe(apiEnvValues.HOME);
expect(subRun.identity).not.toBe(apiRun.identity);
expect(subRun.config.model).toBe(bot.config.model);
expect(apiRun.config.model).toBe(bot.config.model);
expect((subRun.config as Record<string, unknown>).model).toBe(bot.config.model);
expect((apiRun.config as Record<string, unknown>).model).toBe(bot.config.model);
expect(bot).toEqual(original);
expect(aiConnectionBindingSchema.parse(bot.binding)).toEqual(bot.binding);
} finally { await Promise.all([subRun.cleanup(), apiRun.cleanup()]); }
@@ -2005,16 +2005,16 @@ console.log(JSON.stringify({ type: "result", subtype: "success", result: "hello"
const shared = await create("alice", "Engineering", "shared");
const sharedBinding = { ...binding, mode: "shared" as const, ...shared };
const tools = toolAccessService(db);
await tools.replaceConnectionGrantMembers(shared.connectionId, shared.grantId, ["alice"], { userId: "alice" });
await tools.replaceConnectionGrantMembers(shared.connectionId, shared.grantId, ["alice"], { actorType: "user", actorId: "alice" });
const rejection = await service.select({ ...input, userId: "bob", binding: sharedBinding }).catch(error => error);
expect(rejection).toMatchObject({ status: 403, message: "This credential is not shared with the responsible user" });
expect(readAiConnectionConfigurationFailure(rejection)).toBe("ai_connection_credential_not_shared");
expect((await service.list(companyId, "bob", agentId)).some(account => account.id === shared.connectionId)).toBe(false);
await tools.replaceConnectionGrantMembers(shared.connectionId, shared.grantId, ["bob"], { userId: "alice" });
await tools.replaceConnectionGrantMembers(shared.connectionId, shared.grantId, ["bob"], { actorType: "user", actorId: "alice" });
expect((await service.select({ ...input, userId: "bob", binding: sharedBinding })).grant.id).toBe(shared.grantId);
expect((await service.list(companyId, "bob", agentId)).some(account => account.id === shared.connectionId)).toBe(true);
await expect(service.select({ ...input, userId: "alice", binding: sharedBinding })).rejects.toThrow("not shared");
await tools.replaceConnectionGrantMembers(shared.connectionId, shared.grantId, [], { userId: "alice" });
await tools.replaceConnectionGrantMembers(shared.connectionId, shared.grantId, [], { actorType: "user", actorId: "alice" });
for (const userId of ["alice", "bob"]) {
expect((await service.select({ ...input, userId, binding: sharedBinding })).grant.id).toBe(shared.grantId);
}
@@ -2062,7 +2062,7 @@ console.log(JSON.stringify({ type: "result", subtype: "success", result: "hello"
const ae = a.config.env as Record<string,string>, be = b.config.env as Record<string,string>;
expect(ae.ANTHROPIC_API_KEY).toBe("fixture-Alice second"); expect(be.ANTHROPIC_API_KEY).toBe("fixture-Bob first");
expect(ae.HOME).not.toBe(be.HOME); expect(a.identity).not.toBe(b.identity);
expect(a.config.model).toBe("unchanged-model"); expect(config.env.ANTHROPIC_API_KEY).toBe("project-never-use");
expect((a.config as Record<string, unknown>).model).toBe("unchanged-model"); expect(config.env.ANTHROPIC_API_KEY).toBe("project-never-use");
await Promise.all([a.cleanup(), b.cleanup()]); await expect(access(ae.HOME)).rejects.toThrow();
});
it("blocks missing identity, incompatible providers, and cross-company explicit selections", async () => {
@@ -2585,7 +2585,7 @@ console.log(JSON.stringify({ type: "result", subtype: "success", result: "hello"
const selectionInput = { ...input, agentId: futureAgentId, userId: "alice", binding: selected };
await expect(service.select(selectionInput)).rejects.toThrow("not permitted for this agent");
const run = await prepareManagedAiRuntime(db, { companyId, agentId: futureAgentId, responsibleUserId: "alice", adapterType: "claude_local", binding: selected, config: { cwd: home, model: "same-model" }, allowUninstalledShared: true });
expect(run.config.model).toBe("same-model");
expect((run.config as Record<string, unknown>).model).toBe("same-model");
await run.cleanup();
await db.insert(connectionGrantMembers).values({ companyId, grantId: account.grantId, subjectType: "user", subjectId: "bob" });
await expect(service.select({ ...selectionInput, allowUninstalledShared: true })).rejects.toThrow("not shared with the responsible user");
@@ -2629,7 +2629,7 @@ console.log(JSON.stringify({ type: "result", subtype: "success", result: "hello"
await db.insert(heartbeatRuns).values({ id: runId, companyId, agentId: id, status: "failed", errorCode: "acpx_auth_required", responsibleUserId: "alice", contextSnapshot: { issueId } });
interactionId = (await connectionIntentService(db).requestForRunAuthFailure(runId))!.interactionId!;
}
const target = { kind: "remote", transport: "sandbox", remoteCwd: "/workspace", providerKey: "daytona", runner: { execute: vi.fn(async () => ({ exitCode: 0, stdout: "", stderr: "", timedOut: false })) } } as const;
const target = { kind: "remote", transport: "sandbox", remoteCwd: "/workspace", providerKey: "daytona", runner: { execute: vi.fn(async () => ({ exitCode: 0, signal: null, stdout: "", stderr: "", timedOut: false, pid: null, startedAt: null })) } } as const;
const resolveTarget = vi.spyOn(targetModule, "resolveEnvironmentExecutionTarget").mockResolvedValue(target);
const release = vi.fn(async () => undefined);
const acquire = vi.fn(async () => ({ lease: { id: randomUUID(), provider: "daytona", providerLeaseId: "test-sandbox", metadata: {} }, leaseContext: {} }));
@@ -52,7 +52,7 @@ it("reconnect detaches an indexed credential without changing unadopted legacy a
const input = { companyId, agentId, responsibleUserId: owner, adapterType: "claude_local", binding: { provider: "anthropic", method: "api_key", mode: "delegated", connectionId: connection.id, grantId: connection.grantId } as const, config: {}, allowLegacyValidation: true };
const before = await prepareManagedAiRuntime(db, input);
await before.cleanup();
const resolve = () => vault.resolveUserSecretValue(companyId, { definitionId: definition.id, responsibleUserId: owner, required: true, version: "latest" }, { companyId, responsibleUserId: owner, actorType: "system" });
const resolve = () => vault.resolveUserSecretValue(companyId, { definitionId: definition.id, responsibleUserId: owner, required: true, version: "latest" }, { consumerType: "agent", consumerId: agentId, responsibleUserId: owner, actorType: "system" });
expect((await resolve())?.value).toBe("fixture-original");
await service.save(companyId, owner, { provider: "anthropic", method: "api_key", name: connection.name, ownership: "personal", agentIds: [], allAgents: true, connectionId: connection.id, apiKey: "fixture-new-account" }, "fixture-new-account");
expect((await resolve())?.value).toBe("fixture-original");
@@ -155,7 +155,7 @@ it("isolates sign-in and refresh from the host, survives restart, and completes
expect(await readFile(path.join(hostHome, "auth.json"), "utf8")).toBe(auth("legacy"));
const next = await prepareManagedAiRuntime(db, input);
expect(JSON.parse(await readFile(path.join(String(next.config.env.CODEX_HOME), "auth.json"), "utf8")).tokens.refresh_token).toBe("refresh-rotated-independent");
expect(next.config.model).toBe(input.config.model);
expect((next.config as Record<string, unknown>).model).toBe(input.config.model);
await next.cleanup();
// Cancellation after successful completion must not revoke the saved account.
await login.cancel(companyId, owner, attempt.sessionId);
@@ -3,7 +3,7 @@ import { access, readFile, realpath, rm, writeFile } from "node:fs/promises";
import { promises as fs } from "node:fs";
import { join } from "node:path";
import { and, eq, sql } from "drizzle-orm";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi, type MockInstance } from "vitest";
import {
agents, authUsers, closeRegisteredClients, companies, companyMemberships, companySecrets, companySecretVersions,
connectionGrants, createDb, userSecretDefinitions,
@@ -36,7 +36,7 @@ describeDatabase("Google AI connection authority with real PostgreSQL", () => {
let db: ReturnType<typeof createDb>;
let embedded: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>> | undefined;
let isolatedHome: ReturnType<typeof createIsolatedTestHome>;
let request: ReturnType<typeof vi.spyOn<typeof globalThis, "fetch">>;
let request: MockInstance<typeof fetch>;
beforeAll(async () => {
isolatedHome = createIsolatedTestHome();
@@ -190,7 +190,7 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
it("wakes from batch-imported PR work products", async () => {
const target = await issue(), approval = await pending(target.id);
await start(); await quiet();
await workProductService(db).createManyForImport([{ companyId, issueId: target.id, type: "pull_request", provider: "github", title: "Imported PR", status: "open", reviewState: "none", isPrimary: false, healthStatus: "unknown" }]);
await workProductService(db).createManyForImport([{ companyId, issueId: target.id, projectId: null, type: "pull_request", provider: "github", externalId: null, title: "Imported PR", url: null, status: "open", reviewState: "none", isPrimary: false, healthStatus: "unknown", summary: null, metadata: null, executionWorkspaceId: null, runtimeServiceId: null, createdByRunId: null, sourceTrust: null }]);
await seeded(approval.id); await quiet();
});
@@ -207,7 +207,7 @@ const delay = DEFAULT_DECISION_ARCHIVE_DAYS * 86_400_000;
const rows = await db.insert(issues).values(Array.from({ length: 100 }, () => ({ companyId,
title: randomUUID(), status: "in_review", assigneeAgentId: agentId, updatedAt: old() }))).returning();
await db.insert(issueThreadInteractions).values(rows.map(row => ({ companyId, issueId: row.id,
kind: "request_confirmation", addresseeAgentId: agentId, payload: { version: 1, prompt: "Agent verdict" } })));
kind: "request_confirmation" as const, addresseeAgentId: agentId, payload: { version: 1 as const, prompt: "Agent verdict" } })));
statements.length = 0;
const page = await attentionRetentionService(db).load({ companyId, sourceKind: "review" });
expect(page.rows).toEqual([]); expect(page.complete).toBe(false);
@@ -278,11 +278,11 @@ const delay = DEFAULT_DECISION_ARCHIVE_DAYS * 86_400_000;
it("batches 100 blocker graphs with exact terminal timestamps and fresh terminal visibility", async () => {
const roots = await db.insert(issues).values(Array.from({ length: 100 }, () => ({ companyId,
title: randomUUID(), status: "blocked", updatedAt: old(), blockedTransitionAt: old(),
unblockDescriptor: { owner: "board", action: "Pick an owner" } }))).returning();
unblockDescriptor: { owner: "board" as const, action: "Pick an owner" } }))).returning();
const terminals = await db.insert(issues).values(Array.from({ length: 100 }, () => ({ companyId,
title: randomUUID(), status: "backlog", assigneeAgentId: agentId, updatedAt: old() }))).returning();
await db.insert(issueRelations).values(roots.map((root, index) => ({ companyId,
issueId: terminals[index].id, relatedIssueId: root.id, type: "blocks" })));
issueId: terminals[index].id, relatedIssueId: root.id, type: "blocks" as const })));
await db.execute(sql`update issues set updated_at = updated_at + interval '0.000321 seconds' where id = ${terminals[99].id}::uuid`);
const service = attentionRetentionService(db), scope = { companyId, sourceKind: "blocker_attention" as const };
statements.length = 0;
@@ -318,7 +318,7 @@ const delay = DEFAULT_DECISION_ARCHIVE_DAYS * 86_400_000;
{ companyId, issueId: middle.id, relatedIssueId: root.id, type: "blocks" },
{ companyId, issueId: terminal.id, relatedIssueId: middle.id, type: "blocks" },
...heavyNodes.map((node, index) => ({ companyId, issueId: node.id,
relatedIssueId: limit === "nodes" || index === 0 ? heavy.id : heavyNodes[index - 1].id, type: "blocks" })),
relatedIssueId: limit === "nodes" || index === 0 ? heavy.id : heavyNodes[index - 1].id, type: "blocks" as const })),
]);
const owner = issueService(db);
const expected = (await owner.listBlockerAttention(companyId, [root])).get(root.id)!;
@@ -350,7 +350,7 @@ const delay = DEFAULT_DECISION_ARCHIVE_DAYS * 86_400_000;
const cycle = await insertIssue("blocked");
const leaf = await insertIssue("backlog", { assigneeAgentId: agentId });
await db.insert(issueRelations).values([
...chain.map((node, index) => ({ companyId, issueId: node.id, relatedIssueId: index ? chain[index - 1].id : root.id, type: "blocks" })),
...chain.map((node, index) => ({ companyId, issueId: node.id, relatedIssueId: index ? chain[index - 1].id : root.id, type: "blocks" as const })),
{ companyId, issueId: leaf.id, relatedIssueId: cycle.id, type: "blocks" },
{ companyId, issueId: cycle.id, relatedIssueId: leaf.id, type: "blocks" },
]);
@@ -378,7 +378,7 @@ const delay = DEFAULT_DECISION_ARCHIVE_DAYS * 86_400_000;
});
const listed = await issueService(db).list(companyId, { status: "blocked", includeBlockedBy: true });
expect(listed).toHaveLength(roots.length);
for (const root of listed) expect(root.blockerAttention).toMatchObject({ state: "needs_attention", terminalBlockerIssueId: root.id });
for (const root of listed) expect((root as typeof root & { blockerAttention?: unknown }).blockerAttention).toMatchObject({ state: "needs_attention", terminalBlockerIssueId: root.id });
const feed = await attentionService(db).list(companyId, { all: true, allowUnscopedAll: true });
const visible = new Set(feed.items.filter(item => item.sourceKind === "blocker_attention").map(item => item.subject.id));
expect(visible).toEqual(new Set(roots.map(root => root.id)));
@@ -457,7 +457,7 @@ const delay = DEFAULT_DECISION_ARCHIVE_DAYS * 86_400_000;
const [run] = await db.insert(heartbeatRuns).values({ companyId, agentId, status: "running" }).returning();
await db.update(issues).set({ executionRunId: run.id }).where(eq(issues.id, child.id));
} else if (kind === "confirmation") {
await db.insert(issueThreadInteractions).values({ companyId, issueId: child.id, kind: "request_confirmation", status: "pending", payload: { prompt: "Approve?" } });
await db.insert(issueThreadInteractions).values({ companyId, issueId: child.id, kind: "request_confirmation", status: "pending", payload: { version: 1, prompt: "Approve?" } });
}
parameterCounts.length = 0;
const batch = await issueService(db).listBlockerAttentionBatch(companyId, [root]);
@@ -495,7 +495,7 @@ const delay = DEFAULT_DECISION_ARCHIVE_DAYS * 86_400_000;
const [run] = await db.insert(heartbeatRuns).values({ companyId, agentId, status: "running" }).returning();
await db.update(issues).set({ executionRunId: run.id }).where(eq(issues.id, live.id));
} else if (kind === "confirmation") await db.insert(issueThreadInteractions).values({ companyId, issueId: live.id,
kind: "request_confirmation", status: "pending", payload: { prompt: "Still waiting" } });
kind: "request_confirmation", status: "pending", payload: { version: 1, prompt: "Still waiting" } });
}
statements.length = 0; parameterCounts.length = 0;
captureBlockerPlan = kind === "completed";
@@ -687,13 +687,13 @@ const delay = DEFAULT_DECISION_ARCHIVE_DAYS * 86_400_000;
let query!: Parameters<typeof db.execute>[0], returned = 0;
await db.transaction(async tx => {
const execute = tx.execute.bind(tx);
vi.spyOn(tx, "execute").mockImplementation(async statement => {
vi.spyOn(tx, "execute").mockImplementation((async (statement: Parameters<typeof execute>[0]) => {
const result = await execute(statement);
if (new PgDialect().sqlToQuery(statement as SQL).sql.includes("with recursive affected")) {
query = statement; returned = result.length;
}
return result;
});
}) as unknown as typeof execute);
await notifyIssueAttentionRetentionWork(tx, { companyId, issueId: leaf.id });
});
expect(returned).toBe(101);
@@ -804,7 +804,7 @@ const delay = DEFAULT_DECISION_ARCHIVE_DAYS * 86_400_000;
const [decision] = await db.insert(decisions).values({ companyId, originAgentId: agentId, originIssueId: origin.id, originRunId: run.id,
title: "Decision", body: "Choose", options: [], signedSpec: "fixture", targetSnapshots: {}, expiresAt: new Date(Date.now() + 86_400_000), updatedAt: old() }).returning();
const [recovery] = await db.insert(issueRecoveryActions).values({ companyId, sourceIssueId: origin.id, kind: "execution", ownerType: "board", cause: "fixture", fingerprint: "fixture", nextAction: "Choose", updatedAt: old() }).returning();
const [interaction] = await db.insert(issueThreadInteractions).values({ companyId, issueId: origin.id, kind: "ask_user_questions", payload: { questions: [] }, updatedAt: old() }).returning();
const [interaction] = await db.insert(issueThreadInteractions).values({ companyId, issueId: origin.id, kind: "ask_user_questions", payload: { version: 1, questions: [] }, updatedAt: old() }).returning();
await start(); await quiet();
await issueService(db).update(origin.id, { hiddenAt: null });
for (const [kind, id] of [["decision", decision.id], ["recovery_action", recovery.id], ["failed_run", run.id], ["issue_thread_interaction", interaction.id]]) await archived(kind, id);
@@ -933,10 +933,10 @@ const delay = DEFAULT_DECISION_ARCHIVE_DAYS * 86_400_000;
await db.insert(decisions).values({ companyId, originAgentId: agentId, originIssueId: origin.id, originRunId: run.id,
title: "Decision", body: "Choose", options: [], signedSpec: "fixture", targetSnapshots: {}, expiresAt: new Date(Date.now() + 86_400_000), updatedAt: old() });
await db.insert(issueThreadInteractions).values([
{ companyId, issueId: origin.id, kind: "ask_user_questions", payload: { questions: [] }, updatedAt: old() },
{ companyId, issueId: origin.id, kind: "ask_user_questions", payload: { questions: [] }, addresseeAgentId: agentId, updatedAt: old() },
{ companyId, issueId: origin.id, kind: "ask_user_questions", payload: { questions: [] }, addresseeUserId: "board", updatedAt: old() },
{ companyId, issueId: hidden.id, kind: "ask_user_questions", payload: { questions: [] }, updatedAt: old() },
{ companyId, issueId: origin.id, kind: "ask_user_questions", payload: { version: 1, questions: [] }, updatedAt: old() },
{ companyId, issueId: origin.id, kind: "ask_user_questions", payload: { version: 1, questions: [] }, addresseeAgentId: agentId, updatedAt: old() },
{ companyId, issueId: origin.id, kind: "ask_user_questions", payload: { version: 1, questions: [] }, addresseeUserId: "board", updatedAt: old() },
{ companyId, issueId: hidden.id, kind: "ask_user_questions", payload: { version: 1, questions: [] }, updatedAt: old() },
]);
await db.insert(issueRecoveryActions).values({ companyId, sourceIssueId: origin.id, kind: "execution", ownerType: "board", cause: "fixture", fingerprint: "fixture", nextAction: "Choose", updatedAt: old() });
const [policy] = await db.insert(budgetPolicies).values({ companyId, scopeType: "company", scopeId: companyId, windowKind: "calendar_month_utc", amount: 100 }).returning();
@@ -1024,16 +1024,16 @@ const delay = DEFAULT_DECISION_ARCHIVE_DAYS * 86_400_000;
{ companyId, name: "Unhealthy", role: "engineer", status: "active" },
]).returning();
await db.update(agents).set({ reportsTo: manager.id }).where(eq(agents.id, unhealthy.id));
const common = { companyId, issueId: origin.id, kind: "request_confirmation", payload: { prompt: "Approve?" }, createdAt: old(), updatedAt: old() };
const common = { companyId, issueId: origin.id, kind: "request_confirmation", payload: { version: 1, prompt: "Approve?" }, createdAt: old(), updatedAt: old() };
const [older, newest, independent] = await db.insert(issueThreadInteractions).values([
common, { ...common, createdAt: new Date(old().getTime() + 1000) },
{ ...common, payload: { prompt: "Independent", qualityReview: { artifactKey: "copy" } } },
{ ...common, createdAt: new Date(), addresseeUserId: "board" },
{ ...common, addresseeAgentId: agentId },
{ ...common, issueId: hidden.id },
{ ...common, issueId: conversation.id, kind: "ask_user_questions", payload: { questions: [] } },
{ ...common, issueId: conversation.id, kind: "ask_user_questions", payload: { version: 1, questions: [] } },
{ ...common, issueId: conversation.id, addresseeAgentId: paused.id },
{ ...common, issueId: conversation.id, addresseeAgentId: unhealthy.id, kind: "ask_user_questions", payload: { questions: [] } },
{ ...common, issueId: conversation.id, addresseeAgentId: unhealthy.id, kind: "ask_user_questions", payload: { version: 1, questions: [] } },
] as typeof issueThreadInteractions.$inferInsert[]).returning();
const feed = await attentionService(db).list(companyId, { all: true, allowUnscopedAll: true });
const expected = [...new Set(feed.items.map(item => `${item.sourceKind}:${item.subject.id}`))].sort();
@@ -1048,7 +1048,7 @@ const delay = DEFAULT_DECISION_ARCHIVE_DAYS * 86_400_000;
it("excludes maintained reviews until a pending approval creates human attention", async () => {
const row = await insertIssue("in_review", { assigneeAgentId: agentId });
const [interaction] = await db.insert(issueThreadInteractions).values({ companyId, issueId: row.id,
kind: "request_confirmation", addresseeAgentId: agentId, payload: { prompt: "Agent verdict" } }).returning();
kind: "request_confirmation", addresseeAgentId: agentId, payload: { version: 1, prompt: "Agent verdict" } }).returning();
expect((await loadAll()).some(entry => entry.sourceKind === "review")).toBe(false);
const [approval] = await db.insert(approvals).values({ companyId, type: "hire_agent", payload: {}, status: "pending" }).returning();
await db.insert(issueApprovals).values({ companyId, issueId: row.id, approvalId: approval.id });
@@ -1545,8 +1545,8 @@ describeEmbeddedPostgres("attention service", () => {
const issueId = await insertIssue({ companyId, identifier: "ATQ-1", title: "Review copy and visual", status: "in_review", assigneeAgentId: workerId });
const ids = [randomUUID(), randomUUID(), randomUUID(), randomUUID()];
await db.insert(issueThreadInteractions).values(ids.map((id, index) => ({
id, companyId, issueId, kind: "request_confirmation", status: "pending", continuationPolicy: "wake_assignee", createdByAgentId: workerId,
payload: { version: 1, prompt: "Approve output?", ...(index < 2 ? { qualityReview: { key: index === 0 ? "copy" : "visual", requirementHash: "a".repeat(64) } } : {}) },
id, companyId, issueId, kind: "request_confirmation" as const, status: "pending" as const, continuationPolicy: "wake_assignee" as const, createdByAgentId: workerId,
payload: { version: 1 as const, prompt: "Approve output?", ...(index < 2 ? { qualityReview: { key: index === 0 ? "copy" : "visual", requirementHash: "a".repeat(64) } } : {}) },
createdAt: new Date(Date.UTC(2026, 6, 9, 12, index)),
})));
const feed = await attentionService(db).list(companyId, { userId: "board-user" });
@@ -18,7 +18,7 @@ const support = await getEmbeddedPostgresTestSupport();
db = createDb(database.connectionString, { maxConnections: 1 });
client = db.$client;
client.options.transform.value.from = (value, column) => {
if (recording && column.type === 3802 && value !== null) jsonBytes.push(Buffer.byteLength(JSON.stringify(value)));
if (recording && column?.type === 3802 && value !== null) jsonBytes.push(Buffer.byteLength(JSON.stringify(value)));
return value;
};
}, EMBEDDED_POSTGRES_TEST_TIMEOUT_MS);
@@ -1,3 +1,4 @@
import type { Request } from "express";
import { describe, expect, it } from "vitest";
import { HttpError } from "../errors.js";
import { assertBoardOrgAccess, assertCompanyAccess, hasBoardOrgAccess, hasCompanyAccess } from "../routes/authz.js";
@@ -9,7 +10,7 @@ function makeReq(input: {
return {
method: input.method ?? "GET",
actor: input.actor,
} as Express.Request;
} as unknown as Request;
}
describe("assertCompanyAccess", () => {
@@ -1,8 +1,9 @@
import type { Request } from "express";
import { describe, expect, it } from "vitest";
import { buildActorSecretContext } from "../routes/authz.js";
function makeReq(actor: Express.Request["actor"]) {
return { method: "POST", actor } as Express.Request;
return { method: "POST", actor } as unknown as Request;
}
describe("buildActorSecretContext", () => {
@@ -120,7 +120,7 @@ describe("POST /bootstrap/claim", () => {
expect(claimFirstInstanceAdminMock).not.toHaveBeenCalled();
});
it.each([
it.each<[Record<string, unknown>, string]>([
[{ type: "none", source: "none" }, "anonymous caller"],
[{ type: "agent", source: "agent_key", agentId: "agent-1" }, "agent key"],
[{ type: "board", source: "board_key", userId: "user-1" }, "board API key"],
@@ -214,7 +214,7 @@ const support = await getEmbeddedPostgresTestSupport();
if (methodKey.endsWith("oauth")) await f.signIn(connected.connectionId);
else await f.service.reconnectGalleryApp(connected.connectionId, f.company.id, { credentialValues: { "credentials.authorization": f.accessToken } }, actor);
const afterReconnect = await f.service.refreshCatalog(connected.connectionId, actor, { enableAllByDefault: true });
expect(afterReconnect.connection.config.quarantineNewEntries).toBe(true);
expect(afterReconnect.connection.config?.quarantineNewEntries).toBe(true);
expect(afterReconnect.connection.transportConfig.quarantineNewEntries).toBe(true);
expect(afterReconnect.catalog.filter(entry => ["sql_query", "provider_added_tool"].includes(entry.toolName)).every(entry => entry.status === "quarantined")).toBe(true);
f.request.mockClear();
@@ -1165,10 +1165,10 @@ const actor = { actorType: "user" as const, actorId: "browser-reviewer" };
const resizePath = path.replace("/viewer", "/viewport");
const resizeSpy = vi
.spyOn(browserUseViewports, "resize")
.mockResolvedValue({ preset: "phone" });
.mockResolvedValue({ preset: "phone", applied: true });
expect(
(await http(app).post(resizePath).send({ preset: "phone" })).body,
).toEqual({ preset: "phone" });
).toEqual({ preset: "phone", applied: true });
expect(resizeSpy).toHaveBeenCalledTimes(1);
const viewerId = randomUUID();
resizeSpy.mockResolvedValueOnce({
@@ -1457,7 +1457,7 @@ const actor = { actorType: "user" as const, actorId: "browser-reviewer" };
});
it.each(["default_allow", "tool_name"])("retains valid %s profile authorization at browser admission", async selector => {
const f = await fixture();
await db.update(toolProfiles).set({ status: "inactive" }).where(eq(toolProfiles.companyId, f.company.id));
await db.update(toolProfiles).set({ status: "disabled" }).where(eq(toolProfiles.companyId, f.company.id));
const [profile] = await db.insert(toolProfiles).values({ companyId: f.company.id, name: "Scoped browser access",
profileKey: randomUUID(), defaultAction: selector === "default_allow" ? "allow" : "deny" }).returning();
await db.insert(toolProfileBindings).values({ companyId: f.company.id, profileId: profile.id, targetType: "agent", targetId: f.agent.id });
@@ -35,7 +35,7 @@ afterEach(async () => {
});
function setup() {
const execute = vi.fn(async () => [{ xid: "42", status: "committed" }]);
const owner = installDatabaseWorkSignals({ execute, async transaction(callback: (tx: unknown) => Promise<unknown>) {
const owner = installDatabaseWorkSignals({ execute, async transaction(callback: (tx: unknown) => Promise<unknown>): Promise<unknown> {
return callback({ execute, transaction: owner.transaction });
} });
const onError = vi.fn();
@@ -83,8 +83,8 @@ describePostgres("bundled image registration in PostgreSQL", () => {
const [companyA, companyB, unconfigured] = await db.insert(companies).values([
{ name: "Company A", issuePrefix: "CFA" }, { name: "Company B", issuePrefix: "CFB" }, { name: "Unconfigured", issuePrefix: "CFC" },
]).returning();
await registry.upsertConfig(row.id, companyA!.id, { configJson: { token: "only-A" } });
await registry.upsertConfig(row.id, companyB!.id, { configJson: { token: "only-B" } });
await registry.upsertConfig(row.id, companyA!.id, { companyId: companyA!.id, configJson: { token: "only-A" } });
await registry.upsertConfig(row.id, companyB!.id, { companyId: companyB!.id, configJson: { token: "only-B" } });
row = (await registry.getById(row.id))!;
rmSync(old.root, { recursive: true });
const original = { ...row };
@@ -47,7 +47,7 @@ describe("current image plugin runtime loader", () => {
fs.writeFileSync(service, (await transform(source, { loader: "ts", format: "esm", target: "node24" })).code);
const entry = path.join(root, "server", "dist", "probe.js");
fs.writeFileSync(entry, 'import { resolveBundledPluginRuntimeLoader } from "./services/bundled-plugin-runtime.js"; process.stdout.write(resolveBundledPluginRuntimeLoader());');
const bundled = await bundleRuntime({ entry, outdir: path.join(root, "server", "dist-runtime") });
const bundled = await bundleRuntime({ entry, outdir: path.join(root, "server", "dist-runtime"), maintenanceEntry: undefined, ephemeralMigrationEntry: undefined });
expect(execFileSync(process.execPath, [bundled.outfile], { encoding: "utf8" })).toBe(loader);
});
+1 -1
View File
@@ -272,7 +272,7 @@ function makeDeps(overrides?: {
const installedRows = new Map(
Object.entries(rows).map(([key, row]) => [key, normalize(row)] as const),
);
const installPlugin = vi.fn(async ({ localPath }: { localPath: string }) => {
const installPlugin = vi.fn(async ({ localPath }: { localPath: string }): Promise<{ manifest: { id: string } | null }> => {
if (overrides?.installError) throw overrides.installError;
const entry = BUNDLED_PLUGIN_CATALOG.find((candidate) =>
localPath.endsWith(candidate.relativePath),
@@ -76,6 +76,7 @@ describeEmbeddedPostgres("cases routes", () => {
const storage: StorageService = {
provider: "local_disk",
async putFile(input) {
if (!Buffer.isBuffer(input.body)) throw new Error("Expected bounded bytes");
return {
provider: "local_disk",
objectKey: `${input.namespace}/${randomUUID()}`,
File diff suppressed because it is too large. Load diff
@@ -517,9 +517,9 @@ describeEmbeddedPostgres(
});
it.each([
{ kind: "suggest_tasks", payload: { version: 1, tasks: [{ clientKey: "docs", title: "Review the migration", description: "Verify preserved configuration" }] }, expected: "Review the migration" },
{ kind: "request_item_verdicts", payload: { version: 1, prompt: "Review each change", items: [{ id: "docs", label: "Migration notes", description: "Verify preserved configuration" }] }, expected: "Migration notes" },
] as const)("delivers and settles $kind reviews in Slack", async ({ kind, payload, expected }) => {
{ kind: "suggest_tasks" as const, payload: { version: 1 as const, tasks: [{ clientKey: "docs", title: "Review the migration", description: "Verify preserved configuration" }] }, expected: "Review the migration" },
{ kind: "request_item_verdicts" as const, payload: { version: 1 as const, prompt: "Review each change", items: [{ id: "docs", label: "Migration notes", description: "Verify preserved configuration" }] }, expected: "Migration notes" },
])("delivers and settles $kind reviews in Slack", async ({ kind, payload, expected }) => {
const fixture = await seedBoundIssue();
const service = issueThreadInteractionService(db);
const issue = { id: fixture.issueId, companyId: fixture.companyId };
@@ -330,7 +330,7 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
await db.insert(chatPublications).values(ids.map(id => ({
companyId: f.companyId, endpointId: f.endpointId, conversationId: f.conversationId,
issueId: f.issueId, idempotencyKey: `run:${id}:working:${f.endpointId}`,
payload: { text: "Maya is working…", progressState: "working" }, state: "published",
payload: { text: "Maya is working…", progressState: "working" as const }, state: "published" as const,
createdAt: new Date(Date.now() - 60_000),
})));
await db.insert(heartbeatRunEvents).values(ids.map(id => ({
@@ -300,7 +300,7 @@ import { recoveryPostgresSupport as support } from "./helpers/recovery-postgres.
const token = createLocalAgentJwt(f.agentId, f.companyId, "paperclip_runner", f.runId, f.responsibleUserId)!;
const before = await server.db.select({ id: issues.id }).from(issues).where(eq(issues.companyId, f.companyId));
const paths = [`/api/companies/${f.companyId}/issues`, `/api/issues/${f.issueId}/children`];
const selections = [{ projectWorkspaceId: f.projectWorkspaceId }, { executionWorkspaceId: executionWorkspace.id },
const selections: Array<Record<string, string>> = [{ projectWorkspaceId: f.projectWorkspaceId }, { executionWorkspaceId: executionWorkspace.id },
{ inheritExecutionWorkspaceFromIssueId: f.blockerId }];
const create = async (path: string, selection: Record<string, string>) => {
const response = await fetch(`${server.apiUrl}${path}`, {
@@ -37,7 +37,7 @@ const support = await getEmbeddedPostgresTestSupport();
{ id: closedIssueId, companyId, title: "Closed" },
{ id: foreignIssueId, companyId: foreignCompanyId, title: "Foreign" },
]);
await db.insert(toolApplications).values({ id: applicationId, companyId, name: "Slack", type: "native" });
await db.insert(toolApplications).values({ id: applicationId, companyId, name: "Slack", type: "chat" });
await db.insert(toolConnections).values({ id: connectionId, companyId, applicationId, name: "Slack", uid: connectionId, transport: "chat_sdk", connectionPurpose: "channel" });
await db.insert(chatEndpoints).values({ id: endpointId, companyId, connectionId, provider: "slack", publicId: endpointId, assignedAgentId: agentId });
await db.insert(chatConversations).values([
@@ -74,7 +74,7 @@ const support = await getEmbeddedPostgresTestSupport();
(id, company_id, agent_id, status, scope_kind, issue_id, native_issue_id, context_snapshot, updated_at)
values ($1, $2, $3, 'running', $4, $5, $6, $7::jsonb, $8)`,
[runId, companyId, agentId, "runIssueId" in item ? "issue" : "company",
"runIssueId" in item ? item.runIssueId : null, "nativeIssueId" in item ? item.nativeIssueId : null,
"runIssueId" in item ? item.runIssueId ?? null : null, "nativeIssueId" in item ? item.nativeIssueId ?? null : null,
JSON.stringify(item.context), (item.updatedAt ?? since).toISOString()]);
if (item.matches) expectedIds.push(runId);
}
@@ -21,6 +21,7 @@ import { createHeartbeatQueueTestDependencies } from "./helpers/heartbeat-queue.
import { CHAT_CONTROL_RECOVERY_ADMISSION_KEY, CHAT_CONTROL_RECOVERY_UNRESOLVED_CODE } from "../services/chat-control-recovery-stop.js";
import { heartbeatService } from "../services/heartbeat.js";
import { commitNativeStatusDecision, dispatchPendingNativeStatusEffects } from "../services/native-runtime/status-decision-committer.js";
import { NATIVE_STATUS_ARBITER_POLICY_VERSION, type NativeStatusDecision } from "../services/native-runtime/status-arbiter.js";
import { claimQueuedNativeReviewRun } from "../services/native-runtime/native-review-dispatch.js";
import { createChatPublicationResolution } from "../services/chat-channels/publications/resolution.js";
import { createChatPublicationWorker } from "../services/chat-channels/publications/worker.js";
@@ -58,9 +59,12 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
expect(errors).toEqual([]);
});
afterAll(async () => { await db?.$client.end({ timeout: 1 }); await temporary?.cleanup(); });
function chatContextSnapshot(scope = f) {
return { issueId: scope.issueId, source: "chat:slack", endpointId: scope.endpointId, wakeCommentId: scope.commentId };
}
function runValues(scope = f, patch: Partial<typeof heartbeatRuns.$inferInsert> = {}) {
return { id: randomUUID(), companyId: scope.companyId, agentId: scope.agentId, responsibleUserId: scope.userId, status: "running", updatedAt: new Date(),
contextSnapshot: { issueId: scope.issueId, source: "chat:slack", endpointId: scope.endpointId, wakeCommentId: scope.commentId }, ...patch };
contextSnapshot: chatContextSnapshot(scope), ...patch };
}
async function run(scope = f, patch: Partial<typeof heartbeatRuns.$inferInsert> = {}) {
const values = runValues(scope, patch);
@@ -155,7 +159,7 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
let admittedId: string | undefined;
await adapter.withIssueExecutionLock({ companyId: f.companyId, runId: finishing.id, now: new Date() }, async (locked, ports) => {
const input = { companyId: f.companyId, issue: locked.primaryIssue, finishingRun: locked.run,
contextSnapshot: runValues().contextSnapshot, responsibleUserId: f.userId, sessionBefore: null, now: new Date(),
contextSnapshot: chatContextSnapshot(), responsibleUserId: f.userId, sessionBefore: null, now: new Date(),
};
const agent = { id: f.agentId, companyId: f.companyId, name: "Maya", invokable: true };
if (kind === "deferred") {
@@ -186,7 +190,7 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
dependencies.getSchedulingSuppression.mockResolvedValue({ suppressed: true, reason: "task_drain" });
await start(); await flush(); await quiet();
const admitted = await service.enqueueWakeup(f.agentId, { source: "on_demand", reason: "issue_commented",
contextSnapshot: runValues().contextSnapshot, requestedByActorType: "user", requestedByActorId: f.userId,
contextSnapshot: chatContextSnapshot(), requestedByActorType: "user", requestedByActorId: f.userId,
});
expect(admitted).toMatchObject({ status: "queued", companyId: f.companyId });
await flush();
@@ -268,7 +272,7 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
dependencies.getSchedulingSuppression.mockResolvedValue({ suppressed: true, reason: "task_drain" });
await start(); await flush(); await quiet();
const admitted = await service.enqueueWakeup(replacement.id, { source: "on_demand", reason: "issue_commented",
contextSnapshot: runValues().contextSnapshot, requestedByActorType: "user", requestedByActorId: f.userId,
contextSnapshot: chatContextSnapshot(), requestedByActorType: "user", requestedByActorId: f.userId,
});
expect(admitted).toMatchObject({ agentId: replacement.id, status: "queued" });
expect((await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, old.id)))[0])
@@ -326,7 +330,7 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
await db.transaction(async tx => {
await createWakeAdmissionWriter().coalesceIntoActiveExecutionRun(
createAdmissionTransactionScope(f.companyId, tx as unknown as typeof db), {
companyId: f.companyId, activeExecutionRunId: seeded.id, mergedContextSnapshot: runValues().contextSnapshot,
companyId: f.companyId, activeExecutionRunId: seeded.id, mergedContextSnapshot: chatContextSnapshot(),
agentId: f.agentId, source: "on_demand", triggerDetail: null, payload: { issueId: f.issueId },
requestedByActorType: "user", requestedByActorId: f.userId, idempotencyKey: null,
});
@@ -411,11 +415,11 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
});
return { companyId: f.companyId, issueId: f.issueId, runId: native.row.id, assessmentId,
priorStatus: "in_progress", priorStatusVersion: 0, priorDecisionId: null,
decision: { policyVersion: "phase6-v1", statusAction: "preserve" as const, toStatus: "in_progress" as const,
decision: { policyVersion: NATIVE_STATUS_ARBITER_POLICY_VERSION, statusAction: "preserve" as const, toStatus: "in_progress" as const,
reasonCode: "live_continuation_registered", unblockDescriptor: null,
effects: [{ kind: "enqueue_continuation" as const, agentId: f.agentId, continuationKind: "same_agent" as const,
summary: "Continue responding", idempotencyKey: `continuation:${native.row.id}` }],
},
} satisfies NativeStatusDecision,
};
}
it("wakes from a governed native reviewer claim and leaves repeated claims quiet", async () => {
@@ -576,7 +580,7 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
const delivery = questionResponseDeliveryService(db, { heartbeat: {
cancelRun: async () => { throw new Error("The source is already terminal"); },
wakeup: async (_agentId, options) => {
const values = runValues(f, { status: "queued", contextSnapshot: options.contextSnapshot });
const values = runValues(f, { status: "queued", contextSnapshot: options?.contextSnapshot });
const [target] = await db.insert(heartbeatRuns).values(values).returning();
targetId = target.id;
await flush();
@@ -714,6 +718,7 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
let beforeReceiptKeys: string[] | undefined;
await start(); await flush(); await quiet();
const deliver = questionResponseDeliveryService(db, { heartbeat: {
cancelRun: async () => { throw new Error("The source is already terminal"); },
wakeup: async () => {
const [target] = await db.insert(heartbeatRuns).values(runValues(f, {
id: targetId, status: "queued", contextSnapshot: { issueId: f.issueId },
@@ -731,7 +736,7 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
});
// Controlled transport/authorization ports; actual attempt lease, receipt
// transaction, question delivery receipt and milestone worker use PostgreSQL.
const worker = createChatPublicationWorker(db, {
const worker = createChatPublicationWorker(db, ({
CAPABILITIES, settleGitHubAutomaticPublication: async () => false,
ensureDurablePublicationTransport: async publication => publication,
tryProcessTeamsFilePublication: async () => false,
@@ -749,7 +754,7 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
postSafePublication: post, commitTaskControlCompletion: async () => {}, receiptReactionCompletionRunId: async () => null,
finalizePublicationFailure: async (_publication, error) => { errors.push(error); return false; },
redactError: String,
} as Parameters<typeof createChatPublicationWorker>[1]);
} satisfies Partial<Parameters<typeof createChatPublicationWorker>[1]>) as unknown as Parameters<typeof createChatPublicationWorker>[1]);
await worker.processSelectedPublication(prompt);
expect(post).toHaveBeenCalledTimes(1);
expect((await db.select().from(chatPublications).where(eq(chatPublications.id, prompt.id)))[0]).toMatchObject({ state: "published", providerMessageId: "published-question" });
@@ -794,7 +799,7 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
await quiet();
if (resolution) {
const targetId = randomUUID();
await questionResponseDeliveryService(db, { heartbeat: { wakeup: async () => {
await questionResponseDeliveryService(db, { heartbeat: { cancelRun: async () => { throw new Error("The source is already terminal"); }, wakeup: async () => {
const [target] = await db.insert(heartbeatRuns).values(runValues(f, {
id: targetId, status: "queued", contextSnapshot: { issueId: f.issueId },
})).returning();
@@ -804,11 +809,11 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
expect((await publications()).some(row => row.idempotencyKey.startsWith(`run:${targetId}:`))).toBe(false);
await quiet();
const [endpoint] = await db.select().from(chatEndpoints).where(eq(chatEndpoints.id, f.endpointId));
const resolver = createChatPublicationResolution(db, {
const resolver = createChatPublicationResolution(db, ({
endpointRecord: async () => ({ endpoint, assignedAgentName: "Maya", connectionName: "Chat", applicationId: "test", credentialSecretRefs: [] }),
withCredentialMutationLease: async (_endpoint, operation) => operation({ assertOwned: async () => {} }),
commitTaskControlCompletion: async () => {}, processPendingPublications: async () => {},
} as Parameters<typeof createChatPublicationResolution>[1]);
commitTaskControlCompletion: async () => {}, processPendingPublications: async () => 0,
} satisfies Partial<Parameters<typeof createChatPublicationResolution>[1]>) as unknown as Parameters<typeof createChatPublicationResolution>[1]);
await resolver.resolvePublication(f.endpointId, prompt.id, resolution, f.userId);
await flush();
const keys = (await publications()).map(row => row.idempotencyKey);
@@ -928,7 +933,7 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
await start(); await flush(); expect(await publications()).toEqual([]); await quiet();
await db.transaction(async tx => {
await notifyChatRunMilestones(tx, f);
await tx.update(heartbeatRuns).set({ contextSnapshot: runValues().contextSnapshot }).where(eq(heartbeatRuns.id, row.id));
await tx.update(heartbeatRuns).set({ contextSnapshot: chatContextSnapshot() }).where(eq(heartbeatRuns.id, row.id));
});
await flush(); expect(await publications()).toHaveLength(1); await quiet();
});
@@ -177,6 +177,7 @@ type CapturePayload = {
skillEntries: string[];
claudeConfigDir: string | null;
claudeConfigEntries?: string[];
mcpConfigContents?: string | null;
paperclipApiUrl?: string | null;
paperclipApiKey?: string | null;
paperclipApiBridgeMode?: string | null;
@@ -1058,7 +1059,7 @@ describe("claude execute", () => {
const preparedCommand = path.join(remoteWorkspace, ".paperclip-runtime/claude-cli/2.1.280/bin/claude");
const logs: string[] = [];
const commands: string[] = [];
const loggedCommands: string[] = [];
const loggedCommands: Array<string | undefined> = [];
try {
await fs.writeFile(commandPath, (await fs.readFile(commandPath, "utf8")).replace("2.1.286", "2.1.263"));
await fs.mkdir(remoteWorkspace, { recursive: true });
@@ -1081,7 +1082,7 @@ describe("claude execute", () => {
return runner.execute(input);
} } },
authToken: "run-jwt-token", onLog: async (_stream, chunk) => { logs.push(chunk); },
onMeta: async meta => { commands.push(meta.command); loggedCommands.push(meta.env.PAPERCLIP_RESOLVED_COMMAND); },
onMeta: async meta => { commands.push(meta.command); loggedCommands.push(meta.env?.PAPERCLIP_RESOLVED_COMMAND); },
});
if (state === "compatible") {
expect(result.exitCode).toBe(0);
@@ -108,7 +108,7 @@ describe("unclaimed Cloud background work", () => {
const isEnabled = vi.fn().mockResolvedValue(false);
const db = installDatabaseWorkSignals({ execute: vi.fn(), transaction: vi.fn() }) as unknown as Db;
const coordinator = createDeliveryWorkCoordinator({ owner: db, canRun: () => !standby, onError: vi.fn() });
const events = createPublicMcpEvents(db, { isEnabled } as PublicMcpOAuth, vi.fn(), {
const events = createPublicMcpEvents(db, { isEnabled } as unknown as PublicMcpOAuth, vi.fn(), {
isBackgroundWorkEnabled: () => !standby,
});
try {
@@ -1339,7 +1339,7 @@ process.exit(1);
const policy = conversationMode
? buildPaperclipTaskMarkdown({
issue: { id: "issue-1", title: "Chat", workMode: "planning", conversationAgentId: "agent-1" },
issue: { id: "issue-1", identifier: null, title: "Chat", workMode: "planning", conversationAgentId: "agent-1" },
interaction: { kind: "request_confirmation", status: "rejected" },
planReview: { status: "rejected", reason: "Revise the final note." },
includeDescription: false,
@@ -1437,7 +1437,7 @@ process.exit(1);
expect(invocationPrompt).toContain(policy);
if (resumedSession) expect(capture.prompt).not.toContain("Full description that must not replay");
else expect(capture.prompt).toContain("Full description that must not replay");
expect(promptMetrics.taskContextChars).toBe(resumedSession ? policy.length : `Full description that must not replay\n${policy}`.length);
expect(promptMetrics.taskContextChars).toBe(resumedSession ? policy!.length : `Full description that must not replay\n${policy}`.length);
if (conversationMode) {
expect(invocationPrompt).toContain(AGENT_CHAT_DIRECTIVE);
expect(invocationPrompt).toContain("baseRevisionId set to that latestRevisionId");
@@ -60,10 +60,12 @@ describeEmbeddedPostgres("companyService", () => {
lifecycleWorker = startAgentLifecycle(db, { ...createLifecycleDriver(db, {} as never),
requiredPluginIds: async () => [], runPlugin: async () => "complete" }, () => true);
});
const lifecycleStateFor = (status: typeof agents.$inferInsert["status"]): NonNullable<typeof agents.$inferInsert["lifecycleState"]> =>
status === "paused" || status === "pending_approval" || status === "terminated" ? status : "ready";
async function insertAgents(input: typeof agents.$inferInsert | Array<typeof agents.$inferInsert>) {
const rows = Array.isArray(input) ? input : [input];
await db.insert(agents).values(rows.map(row => ({ ...row,
lifecycleState: ["paused", "pending_approval", "terminated"].includes(row.status ?? "") ? row.status! : "ready",
lifecycleState: lifecycleStateFor(row.status),
lifecycleHolds: row.status === "paused" ? [row.pauseReason ?? "manual"] : [],
})));
}
@@ -21,7 +21,7 @@ const companySvc = {
// Async-empty default (not a bare vi.fn()): every new-company import reads
// the existing names for de-duplication, including describes that never
// touch this mock.
list: vi.fn(async () => []),
list: vi.fn(async (): Promise<Array<{ name: string }>> => []),
create: vi.fn(),
update: vi.fn(),
};
@@ -516,7 +516,7 @@ describe("company portability", () => {
entryFile: "AGENTS.md",
warnings: [],
}));
agentInstructionsSvc.materializeManagedBundle.mockImplementation(async (agent: { adapterConfig: Record<string, unknown> }) => ({
agentInstructionsSvc.materializeManagedBundle.mockImplementation(async (agent: { id: string; adapterConfig: Record<string, unknown> }) => ({
bundle: null,
adapterConfig: {
...agent.adapterConfig,
@@ -5,6 +5,10 @@ import { issueRoutes } from "../routes/issues.js";
import { createCompanySearchRateLimiter } from "../services/company-search-rate-limit.js";
import type { CompanySearchQuery, CompanySearchResponse } from "@paperclipai/shared";
async function unusedExtract(): Promise<never> {
throw new Error("Rate limit tests do not call extract");
}
function createSearchResponse(query: CompanySearchQuery): CompanySearchResponse {
return {
query: query.q,
@@ -44,7 +48,7 @@ describe("company search route rate limiting", () => {
next();
});
app.use("/api", issueRoutes({} as never, {} as never, {
searchService: { search },
searchService: { search, extract: unusedExtract },
searchRateLimiter: createCompanySearchRateLimiter({
maxRequests: 1,
windowMs: 60_000,
@@ -76,7 +80,7 @@ describe("company search route rate limiting", () => {
next();
});
app.use("/api", issueRoutes({} as never, {} as never, {
searchService: { search },
searchService: { search, extract: unusedExtract },
searchRateLimiter: createCompanySearchRateLimiter({
maxRequests: 10,
windowMs: 60_000,
@@ -104,7 +108,7 @@ describe("company search route rate limiting", () => {
next();
});
app.use("/api", issueRoutes({} as never, {} as never, {
searchService: { search },
searchService: { search, extract: unusedExtract },
searchRateLimiter: createCompanySearchRateLimiter({
maxRequests: 10,
windowMs: 60_000,
@@ -80,7 +80,6 @@ describeEmbeddedPostgres("company skill policy routes", () => {
source: "agent_key",
agentId,
companyId: actor === "cross-company" ? otherCompanyId : companyId,
runId: null,
};
next();
});
@@ -1,4 +1,4 @@
import Ajv2020 from "ajv/dist/2020.js";
import { Ajv2020 } from "ajv/dist/2020.js";
import { requestHumanInputAction } from "../../../packages/paperclip-runner/src/protocol-actions/request-human-input.js";
import { questionSetToAskUserQuestionsPayload } from "@paperclipai/shared";
import { randomUUID } from "node:crypto";
@@ -240,7 +240,7 @@ async function createRemoteMcpToolFixture(db: ReturnType<typeof createDb>, compa
describe("Honcho workspace configuration", () => {
const schema = { type: "object", properties: { workspace_id: { type: "string" }, query: { type: "string" } }, required: ["workspace_id", "query"] };
const connection = { config: { sourceTemplateKey: "honcho", connectionMethodKey: "mcp-api-key", methodConfig: { workspaceId: "team-workspace" } } } as typeof toolConnections.$inferSelect;
const connection = { transport: "mcp_remote" as const, config: { sourceTemplateKey: "honcho", connectionMethodKey: "mcp-api-key", methodConfig: { workspaceId: "team-workspace" } } };
it("projects the workspace and hides its argument while keeping old unconfigured tools usable", () => {
expect(projectedConnectionToolArguments(connection, { workspace_id: "forged", query: "decisions" }, "query", schema)).toEqual({ workspace_id: "team-workspace", query: "decisions" });
expect(projectedConnectionToolInputSchema(connection, schema, "query")).toEqual({ type: "object", properties: { query: { type: "string" } }, required: ["query"] });
@@ -980,7 +980,7 @@ describeEmbeddedPostgres("connectionIntentService", () => {
const access = toolAccessService(db);
const effective = await access.getEffectiveProfilesForAgent(f.company.id, f.agent.id);
const profile = effective.profiles.find(profile => profile.profileKey.startsWith("connection-intent:"))!;
await access.deleteProfile(profile.id, {});
await access.deleteProfile(profile.id, { force: false });
expect((await access.getEffectiveProfilesForAgent(f.company.id, f.agent.id)).allowedTools).toEqual([]);
for (const tool of f.catalog.slice(0, 2)) {
const decision = await toolAccessPolicyService(db).decide({
@@ -79,7 +79,7 @@ describe("connection management with production default agents", () => {
await server.db.update(heartbeatRuns).set({ agentId: agent.id }).where(eq(heartbeatRuns.id, base.runId));
await server.db.update(issues).set({ assigneeAgentId: agent.id, responsibleUserId: userId }).where(eq(issues.id, base.issueId));
const key = await agentService(server.db).createApiKey(agent.id, "integration", { kind: "standard" }, { responsibleUserId: userId });
const jwt = createLocalAgentJwt(agent.id, base.companyId, "paperclip_runner", base.runId, userId);
const jwt = createLocalAgentJwt(agent.id, base.companyId, "paperclip_runner", base.runId, userId)!;
const request = async (method: string, path: string, body?: unknown, token = key.token) => {
const response = await fetch(server.apiUrl + (path.startsWith("/mcp/") ? "" : "/api") + path, { method, headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }) });
return { status: response.status, body: await response.json().catch(() => null) };
@@ -480,7 +480,7 @@ describe("connection management with production default agents", () => {
try {
const [environment] = await server.db.insert(environments).values({ name: `Login fixture ${f.agentId}`, driver: "sandbox" }).returning();
const [session] = await server.db.insert(adapterAuthSessions).values({ companyId: f.companyId, environmentId: environment.id, adapterType: "codex_local", startedByUserId: f.userId,
publicSessionId: randomUUID(), status: "waiting_for_user", expiresAt: new Date(Date.now() + 60_000), aiConnection: { ownership: "personal", name: "Saved login", allAgents: false, agentIds: [] } }).returning();
publicSessionId: randomUUID(), status: "waiting_for_user", expiresAt: new Date(Date.now() + 60_000), aiConnection: { provider: "openai", method: "subscription", ownership: "personal", name: "Saved login", allAgents: false, agentIds: [] } }).returning();
const path = `/companies/${f.companyId}/adapters/codex_local/login-sessions`;
expect(await f.request("GET", `${path}/active`)).toMatchObject({ status: 200, body: { sessionId: session.publicSessionId } });
expect(await f.request("GET", `${path}/${session.publicSessionId}`, undefined, f.jwt)).toMatchObject({ status: 200, body: { sessionId: session.publicSessionId } });
@@ -569,7 +569,7 @@ describe("connection management with production default agents", () => {
}
if (revocation === "other-agent-credential" || revocation === "other-agent-credential-none") {
await server.db.update(toolConnections).set({ authKind: revocation === "other-agent-credential-none" ? "none" : "api_key", credentialPolicy: "per_agent" }).where(eq(toolConnections.id, created.body.id));
await server.db.insert(connectionGrants).values([f.agentId, targetAgentId].map(subjectAgentId => ({ companyId: f.companyId, connectionId: created.body.id, kind: "agent" as const, subjectAgentId, status: "active" })));
await server.db.insert(connectionGrants).values([f.agentId, targetAgentId].map(subjectAgentId => ({ companyId: f.companyId, connectionId: created.body.id, kind: "agent" as const, subjectAgentId, status: "active" as const })));
expect(await f.request("POST", path, body, f.jwt)).toMatchObject({ status: 403, body: { reasonCode: "test_credential_identity_mismatch" } });
expect(calls).toBe(0);
return;
@@ -688,7 +688,7 @@ describe("connection management with production default agents", () => {
expect(settled.policyExplanation?.delegatedCallerConsumedRateAdmissions).toHaveLength(1);
const [nextRun] = await server.db.insert(heartbeatRuns).values({ companyId: f.companyId, agentId: f.agentId,
status: "running", responsibleUserId: f.userId, contextSnapshot: { issueId: f.issueId } }).returning();
const nextJwt = createLocalAgentJwt(f.agentId, f.companyId, "paperclip_runner", nextRun.id, f.userId);
const nextJwt = createLocalAgentJwt(f.agentId, f.companyId, "paperclip_runner", nextRun.id, f.userId)!;
expect(await f.request("POST", path, body, nextJwt)).toMatchObject({ status: 200, body: { decision: "off", error: { reasonCode: "rate_limited" } } });
expect(calls).toBe(1);
}
@@ -732,7 +732,7 @@ describe("connection management with production default agents", () => {
const ownBinding = await f.request("POST", `${path}/bind`, { targetType: "issue", targetId: f.issueId });
expect(ownBinding, JSON.stringify(ownBinding)).toMatchObject({ status: 201 });
expect(await f.request("POST", `${path}/unbind`, { targetType: "issue", targetId: f.issueId })).toMatchObject({ status: 200 });
for (const target of [{ targetType: "project", targetId: f.projectId }, { targetType: "issue", targetId: issue.id }, { targetType: "routine", targetId: routine.id }, { targetType: "gateway", targetId: gateway.body.id }]) {
for (const target of [{ targetType: "project" as const, targetId: f.projectId }, { targetType: "issue" as const, targetId: issue.id }, { targetType: "routine" as const, targetId: routine.id }, { targetType: "gateway" as const, targetId: gateway.body.id }]) {
expect(await f.request("POST", `${path}/bind`, target), JSON.stringify(target)).toMatchObject({ status: 403 });
const [saved] = await server.db.insert(toolProfileBindings).values({ ...target, companyId: f.companyId, profileId: profile.body.id }).returning();
expect(await f.request("PATCH", `/tool-profiles/${profile.body.id}`, { description: "Cannot edit retained target" })).toMatchObject({ status: 403 });
@@ -786,7 +786,7 @@ describe("connection management with production default agents", () => {
return originalFrom(table);
}) as typeof builder.from;
return builder;
}) as typeof server.db.select);
}) as unknown as typeof server.db.select);
try {
const path = `/companies/${f.companyId}/tools/policies`;
const body = { name: "Bounded policy", policyType: "allow", selectors: { agentId: f.agentId, connectionIds: Array(5000).fill(created.body.id) } };
@@ -1414,7 +1414,7 @@ describe("connection management with production default agents", () => {
externalConversationId: randomUUID(), externalLabel: "History" }))).returning();
const rows = Array.from({ length: 30 }, (_, index) => ({ id: `${f.agentId.slice(0, 24)}${index.toString(16).padStart(12, "0")}`,
companyId: f.companyId, endpointId: endpoint.body.id, conversationId: conversations[index < 3 ? 0 : 1].id,
providerEventId: randomUUID(), deduplicationKey: randomUUID(), eventKind: index < 3 ? "readable_history" : "private_history", state: "processed", normalizedEvent: {}, createdAt: new Date("2026-10-25T12:00:00.000Z") }));
providerEventId: randomUUID(), deduplicationKey: randomUUID(), eventKind: "message" as const, state: "processed" as const, redactedError: index < 3 ? null : "private_history", normalizedEvent: {}, createdAt: new Date("2026-10-25T12:00:00.000Z") }));
await server.db.insert(chatDeliveries).values(rows);
const path = `/chat-endpoints/${endpoint.body.id}/activity?limit=25`;
const first = await f.request("GET", path, undefined, f.jwt);
@@ -1594,7 +1594,7 @@ describe("connection management with production default agents", () => {
if (mode === "ask-rate") await server.db.insert(toolPolicies).values({ companyId: f.companyId, name: "One issuer admission", policyType: "rate_limit", selectors: { connectionId: connection.body.id, agentId: f.agentId }, priority: -1, config: { limit: 1, windowSeconds: 60, keyBy: ["agent", "tool"] } });
if (mode === "credential") {
await server.db.update(toolConnections).set({ credentialPolicy: "per_agent" }).where(eq(toolConnections.id, connection.body.id));
await server.db.insert(connectionGrants).values([f.agentId, target.id].map(subjectAgentId => ({ companyId: f.companyId, connectionId: connection.body.id, kind: "agent" as const, subjectAgentId, status: "active" })));
await server.db.insert(connectionGrants).values([f.agentId, target.id].map(subjectAgentId => ({ companyId: f.companyId, connectionId: connection.body.id, kind: "agent" as const, subjectAgentId, status: "active" as const })));
}
const called = await rpc("tools/call", { name: tool.name, arguments: {} });
if (isRate) {
@@ -1896,7 +1896,7 @@ describe("connection management with production default agents", () => {
await server.db.update(agentApiKeys).set({ responsibleUserId: null }).where(eq(agentApiKeys.id, f.key.id));
expect((await f.request("GET", `/companies/${f.companyId}/tools/gallery`)).status).toBeGreaterThanOrEqual(400);
const g = await fixture();
await server.db.update(agentApiKeys).set({ scopeConfig: { kind: "task_bridge", issueId: g.issueId } }).where(eq(agentApiKeys.id, g.key.id));
await server.db.update(agentApiKeys).set({ scopeConfig: { kind: "task_bridge", parentIssueId: g.issueId } }).where(eq(agentApiKeys.id, g.key.id));
expect((await g.request("GET", `/companies/${g.companyId}/tools/gallery`)).status).toBeGreaterThanOrEqual(400);
});
@@ -104,7 +104,8 @@ const support = await getEmbeddedPostgresTestSupport();
remoteHttpEndpointLookup: async () => [{ address: "8.8.8.8", family: 4 }] });
const actor = { actorType: "user" as const, actorId: "policy-reviewer" };
const connection = await service.createConnection(company.id, {
name: "Custom reviewed endpoint", transport: "mcp_remote", enabled: true, status: "active",
name: "Custom reviewed endpoint", connectionPurpose: "tool", transport: "mcp_remote", authKind: "none", ownership: "customer", connectionKind: "managed",
transportConfig: {}, credentialSecretRefs: [], enabled: true, status: "active",
config: { sourceTemplateKey: "custom", url: RAILWAY_MCP_URL, quarantineNewEntries: true },
}, actor);
const initial = await service.refreshCatalog(connection.id, actor, { enableAllByDefault: true });
@@ -112,8 +113,8 @@ const support = await getEmbeddedPostgresTestSupport();
tools = [...tools, { name: "provider_added_tool", annotations: { readOnlyHint: true } }];
const refreshed = await service.refreshCatalog(connection.id, actor, { enableAllByDefault: true });
expect(refreshed.catalog.find(tool => tool.toolName === "provider_added_tool")).toMatchObject({ riskLevel: "write", status: "quarantined" });
expect(refreshed.connection.config.quarantineNewEntries).toBe(true);
expect(refreshed.connection.config.sourceTemplateKey).toBe("custom");
expect(refreshed.connection.config?.quarantineNewEntries).toBe(true);
expect(refreshed.connection.config?.sourceTemplateKey).toBe("custom");
expect(request).toHaveBeenCalled();
});
});
@@ -36,7 +36,7 @@ const support = await getEmbeddedPostgresTestSupport();
if (reply === "response") await db.insert(issueComments).values({ companyId, issueId,
createdByRunId: runId, authorAgentId: agentId, origin: "comment", body: padding });
if (reply === "interaction") await db.insert(issueThreadInteractions).values({ companyId, issueId,
sourceRunId: runId, createdByAgentId: agentId, status: "pending", kind: "request_confirmation", payload: { version: 1 } });
sourceRunId: runId, createdByAgentId: agentId, status: "pending", kind: "request_confirmation", payload: { version: 1, prompt: "Approve?" } });
return { companyId, agentId, issueId, runId, wakeId, wakeTime };
}
@@ -1,4 +1,4 @@
import { createServer } from "node:http";
import { createServer, type RequestListener } from "node:http";
import type { AddressInfo } from "node:net";
import { generateKeyPairSync, randomUUID } from "node:crypto";
import { mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
@@ -118,7 +118,7 @@ describe.skipIf(!cloudDist)("customer-success managed agent / Cloud / tenant qua
.values({ companyId: customer.id, projectId: project.id, title: "First successful task" })
.returning();
const storage = { provider: "local_disk" } as StorageService;
const start = async (handler: Parameters<typeof createServer>[0]) => {
const start = async (handler: RequestListener) => {
const server = createServer(handler);
servers.push(server);
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
@@ -289,7 +289,7 @@ describe.skipIf(!cloudDist)("customer-success managed agent / Cloud / tenant qua
env: { PAPERCLIP_CUSTOMER_SUCCESS_CLOUD_ORIGIN: cloudOrigin },
},
context: {},
runtime: { sessionId: null, sessionParams: null, taskKey: null },
runtime: { sessionId: null, sessionParams: null, sessionDisplayId: null, taskKey: null },
onLog: async (_stream, chunk) => {
output.push(chunk);
},
@@ -559,7 +559,7 @@ describe("customer-success read authority", () => {
companyId,
companySkillId: skill.id,
revisionNumber: 1,
fileInventory: [{ path: "SKILL.md", kind: "markdown", sizeBytes: 9, content: "# Example" }],
fileInventory: [{ path: "SKILL.md", kind: "markdown", content: "# Example" }],
})
.returning();
await db
@@ -49,7 +49,7 @@ const credential = "daytona-fixture-secret";
throw new Error("Unexpected provider request");
});
const service = toolAccessService(db, { remoteHttpRequest: request, remoteHttpEndpointLookup: async () => [{ address: "8.8.8.8", family: 4 }] });
const connected = await service.connectGalleryApp(company.id, { galleryKey: "daytona", methodKey: "workspace-diagnostics",
const connected = await service.connectGalleryApp(company.id, { galleryKey: "daytona", connectionMethodKey: "workspace-diagnostics",
...(personal ? { grantKind: "user" as const } : {}), credentialValues: { "credentials.authorization": credential } }, actor);
const [agent] = await db.insert(agents).values({ companyId: company.id, name: "Maintainer", role: "engineer", adapterType: "process" }).returning();
await service.finishGalleryAppConnection(company.id, connected.connectionId, { enabledCatalogEntryIds: connected.catalog.map(c => c.id),
@@ -156,7 +156,7 @@ const credential = "daytona-fixture-secret";
const legacy = await db.insert(toolCatalogEntries).values(legacyNames.map(toolName => ({
companyId: f.company.id, applicationId: f.connected.connection.applicationId, connectionId: f.connected.connectionId,
name: toolName, toolName, title: toolName === "daytona_get_workspace_recovery" ? "Get workspace recovery" : toolName,
entryKind: "tool", status: "active", riskLevel: "read", isReadOnly: true,
entryKind: "tool" as const, status: "active" as const, riskLevel: "read" as const, isReadOnly: true,
inputSchema: { type: "object" }, versionHash: "legacy", schemaHash: "legacy",
}))).returning();
await f.service.finishGalleryAppConnection(f.company.id, f.connected.connectionId, {
+5 -5
View File
@@ -16,7 +16,7 @@ it.each([
])("preserves $due continuation with $spacing ms minimum spacing and disarms after settlement", async ({ due, spacing, delay }) => {
vi.useFakeTimers();
const onError = vi.fn();
const owner = installDatabaseWorkSignals({ execute: vi.fn(async () => []) });
const owner = installDatabaseWorkSignals({ execute: vi.fn(async () => []), transaction: vi.fn() });
coordinator = createDeliveryWorkCoordinator({ owner, canRun: () => true, onError });
const now = Date.now();
const run = vi.fn(async () => {});
@@ -68,7 +68,7 @@ it("releases a settled first-write hold during drain and retains a probe-free re
it("retains failed work through drain and preserves a future domain deadline", async () => {
vi.useFakeTimers();
let draining = false;
const owner = installDatabaseWorkSignals({ execute: vi.fn(async () => []) });
const owner = installDatabaseWorkSignals({ execute: vi.fn(async () => []), transaction: vi.fn() });
const run = vi.fn(async () => { if (run.mock.calls.length === 1) { draining = true; throw new Error("retry debt"); } });
const deadline = Date.now() + 600_000;
const next = vi.fn(async () => deadline);
@@ -87,7 +87,7 @@ it("retains failed work through drain and preserves a future domain deadline", a
it("does no domain probes in warm standby and reconstructs after admission opens", async () => {
vi.useFakeTimers();
let standby = true;
const owner = installDatabaseWorkSignals({ execute: vi.fn(async () => []) });
const owner = installDatabaseWorkSignals({ execute: vi.fn(async () => []), transaction: vi.fn() });
const run = vi.fn(async () => {}), next = vi.fn(async () => null);
coordinator = createDeliveryWorkCoordinator({ owner, canRun: () => !standby, canReconcile: () => !standby, onError: vi.fn() });
const worker = registerDeadlineWork(coordinator, DELIVERY_QUEUES.agentTimers, { run, next, canRun: () => !standby, retryMs: 30_000 });
@@ -126,7 +126,7 @@ it("does not release an ambiguous database intent while dispatch is drained", as
it("backs off an expired deadline when only the domain is drained and resumes promptly", async () => {
vi.useFakeTimers();
let paused = false;
const owner = installDatabaseWorkSignals({ execute: vi.fn(async () => []) });
const owner = installDatabaseWorkSignals({ execute: vi.fn(async () => []), transaction: vi.fn() });
const run = vi.fn(async () => {});
const domainCanRun = vi.fn(() => !paused);
const deadline = Date.now() + 1_000;
@@ -155,7 +155,7 @@ it("backs off an expired deadline when only the domain is drained and resumes pr
it("retains a prompt paused wake when a new request preempts a far-future deadline", async () => {
vi.useFakeTimers();
let paused = false;
const owner = installDatabaseWorkSignals({ execute: vi.fn(async () => []) });
const owner = installDatabaseWorkSignals({ execute: vi.fn(async () => []), transaction: vi.fn() });
const run = vi.fn(async () => {});
const deadline = Date.now() + 86_400_000;
coordinator = createDeliveryWorkCoordinator({ owner, canRun: () => true, onError: vi.fn() });
@@ -87,7 +87,7 @@ const support = await getEmbeddedPostgresTestSupport();
kind: "request_item_verdicts", status: "answered", resolvedAt: new Date(),
payload: { version: 1, prompt: "Review", items: [] },
result: { version: 1, outcome: "resolved", complete: true,
items: verdicts.map((verdict, index) => ({ id: String(index), verdict })) } });
items: verdicts.map((verdict, index) => ({ id: String(index), verdict, resolvedAt: new Date().toISOString() })) } });
}
await db.insert(issueThreadInteractions).values([
{ companyId, issueId, title: "Question answered", kind: "ask_user_questions", status: "answered", resolvedAt: new Date(), payload: { version: 1, questions: [] } },
@@ -19,6 +19,7 @@ import {
issues,
} from "@paperclipai/db";
import type { AttentionItem } from "@paperclipai/shared";
import { DEFAULT_DECISION_SHELF_DAYS } from "../services/decision-retention.js";
import {
getEmbeddedPostgresTestSupport,
startEmbeddedPostgresTestDatabase,
@@ -323,6 +324,18 @@ describeEmbeddedPostgres("decision queue routes", () => {
} : null,
project: null,
workspace: null,
expiresAt: null,
ruleKey: null,
originAgentName: null,
queues: [],
shelf: false,
retentionDays: DEFAULT_DECISION_SHELF_DAYS,
keep: false,
archivedAt: null,
retentionVersion: 0,
decideBy: null,
decideByAttribution: null,
snoozedUntil: null,
detail: null,
trainingExampleId: null,
};
@@ -24,6 +24,7 @@ import {
import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.js";
import { attentionService } from "../services/attention.js";
import { decisionService } from "../services/decisions.js";
import { signDecisionSpec } from "../services/decision-signing.js";
import * as retentionModule from "../services/decision-retention.js";
import { hashAttentionArchiveManifest } from "../services/decision-retention.js";
import type { AttentionArchiveManifestEntry, AttentionArchiveTargetSnapshot } from "@paperclipai/shared";
@@ -395,9 +396,12 @@ describePg("decisionService", () => {
const created = await service().create({
companyId, actor: agentActor(), agentId, runId, title: "Cancel?", body: "Body",
options: [{ id: "yes", label: "Yes", effects: [{
type: "cancel_issue_tree", targetIssueId, staleness: "lenient", reasonComment: "cleanup",
type: "cancel_issue_tree", targetIssueId, staleness: "strict", reasonComment: "cleanup",
}] }],
});
// The effect type admits only strict cancellation, so a lenient one can exist only as a stored, signed row.
const [stored] = await db.update(decisions).set({ options: sql`jsonb_set(${decisions.options}, '{0,effects,0,staleness}', '"lenient"')` }).where(eq(decisions.id, created.id)).returning();
await db.update(decisions).set({ signedSpec: signDecisionSpec({ decisionId: stored!.id, options: stored!.options, targetSnapshots: stored!.targetSnapshots }) }).where(eq(decisions.id, created.id));
const unreviewedChildId = randomUUID();
await db.insert(issues).values({ id: unreviewedChildId, companyId, title: "Unreviewed child", status: "todo", priority: "medium",
parentId: targetIssueId, responsibleUserId: decidedByUserId });
@@ -213,7 +213,7 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
it("delivers reviewed REST actions through native assignments without admitting unsupported or inactive connections", async () => {
vi.stubEnv("PAPERCLIP_API_URL", "http://127.0.0.1:3100");
onTestFinished(() => vi.unstubAllEnvs());
onTestFinished(() => { vi.unstubAllEnvs(); });
const f = await fixture(provider, true);
await f.service.putConnectionInstalls(f.connected.connectionId, {
installs: [{ targetType: "agent", targetId: f.agent.id }],
@@ -67,7 +67,7 @@ describe("paperclip runner native dev prerequisite", () => {
Object.defineProperty(process, "arch", { ...initialArchitecture, value: architecture });
const nativeRunnerRequired = paperclipRunnerSupportsPlatform("codex_local", "linux", architecture, glibcVersionRuntime ? "glibc" : "unknown", glibcVersionRuntime as string | undefined);
expect(nativeRunnerRequired).toBe(native);
expect(paperclipRunnerBinaryNeedsBuild({ repoRoot: "/checkout/without/a/staged/binary", nativeRunnerRequired })).toBe(native);
expect(paperclipRunnerBinaryNeedsBuild({ repoRoot: "/checkout/without/a/staged/binary", nativeRunnerRequired, configuredBinary: undefined })).toBe(native);
});
it("uses an explicit status response and fails safe when status is unknown", () => {
expect(
@@ -109,6 +109,7 @@ describe("paperclip runner native dev prerequisite", () => {
paperclipRunnerBinaryNeedsBuild({
repoRoot: checkout.root,
nativeRunnerRequired: true,
configuredBinary: undefined,
}),
).toBe(false);
@@ -117,6 +118,7 @@ describe("paperclip runner native dev prerequisite", () => {
paperclipRunnerBinaryNeedsBuild({
repoRoot: checkout.root,
nativeRunnerRequired: true,
configuredBinary: undefined,
}),
).toBe(true);
@@ -125,6 +127,7 @@ describe("paperclip runner native dev prerequisite", () => {
paperclipRunnerBinaryNeedsBuild({
repoRoot: checkout.root,
nativeRunnerRequired: true,
configuredBinary: undefined,
}),
).toBe(true);
});
@@ -134,6 +137,7 @@ describe("paperclip runner native dev prerequisite", () => {
paperclipRunnerBinaryNeedsBuild({
repoRoot: "/checkout/without/a/staged/binary",
nativeRunnerRequired: false,
configuredBinary: undefined,
}),
).toBe(false);
});
@@ -148,14 +152,14 @@ describe("paperclip runner native dev prerequisite", () => {
const now = Date.now(), old = new Date(now - 2_000), built = new Date(now + 2_000), changed = new Date(now + 4_000);
fs.utimesSync(dependency, old, old);
fs.utimesSync(checkout.binary, built, built);
const options = { repoRoot: checkout.root, nativeRunnerRequired: true };
const options = { repoRoot: checkout.root, nativeRunnerRequired: true, configuredBinary: undefined };
expect(paperclipRunnerBinaryNeedsBuild(options)).toBe(false);
fs.utimesSync(dependency, changed, changed);
expect(paperclipRunnerBinaryNeedsBuild(options)).toBe(true);
expect(paperclipRunnerBinaryNeedsBuild({ ...options, nativeRunnerRequired: false })).toBe(false);
});
it.each(["arm64", "x64"])("uses the staged Mac %s target before the flat build output", architecture => {
it.each(["arm64", "x64"] as const)("uses the staged Mac %s target before the flat build output", architecture => {
const checkout = createRunnerCheckout();
const target = path.join(path.dirname(checkout.binary), `darwin-${architecture}`, "paperclip-runnerd");
fs.mkdirSync(path.dirname(target), { recursive: true });
@@ -163,7 +167,7 @@ describe("paperclip runner native dev prerequisite", () => {
const old = new Date(Date.now() - 2000), current = new Date(Date.now() + 2000);
fs.utimesSync(checkout.binary, old, old);
fs.utimesSync(target, current, current);
const input = { repoRoot: checkout.root, nativeRunnerRequired: true, platform: "darwin", architecture };
const input = { repoRoot: checkout.root, nativeRunnerRequired: true, configuredBinary: undefined, platform: "darwin" as const, architecture };
expect(paperclipRunnerBinaryNeedsBuild(input)).toBe(false);
fs.writeFileSync(checkout.binary, nativeHeader("linux", "x64"));
const wrongHostNewer = new Date(Date.now() + 4000);
@@ -178,7 +182,7 @@ describe("paperclip runner native dev prerequisite", () => {
expect(paperclipRunnerBinaryNeedsBuild(input)).toBe(true);
});
it.each(["arm64", "x64"])("uses only a newer matching Mac %s source build without changing release selection", architecture => {
it.each(["arm64", "x64"] as const)("uses only a newer matching Mac %s source build without changing release selection", architecture => {
const checkout = createRunnerCheckout();
const target = path.join(path.dirname(checkout.binary), `darwin-${architecture}`, "paperclip-runnerd");
fs.mkdirSync(path.dirname(target), { recursive: true });
@@ -186,7 +190,7 @@ describe("paperclip runner native dev prerequisite", () => {
fs.writeFileSync(checkout.binary, nativeHeader("darwin", architecture));
const old = new Date(Date.now() - 2000), fresh = new Date(Date.now() + 2000), changed = new Date(Date.now() + 4000);
fs.utimesSync(target, old, old); fs.utimesSync(checkout.binary, fresh, fresh);
const input = { repoRoot: checkout.root, nativeRunnerRequired: true, platform: "darwin", architecture };
const input = { repoRoot: checkout.root, nativeRunnerRequired: true, configuredBinary: undefined, platform: "darwin" as const, architecture };
expect(resolveNativeRunnerDevBinary(input)).toEqual({ binary: checkout.binary, sourceBuild: true });
expect(paperclipRunnerBinaryNeedsBuild(input)).toBe(false);
fs.utimesSync(checkout.source, changed, changed);
@@ -32,7 +32,7 @@ describe("createCapturedOutputBuffer", () => {
headers: { "content-type": "application/json" },
});
await expect(parseJsonResponseWithLimit<{ ok: boolean }>(response, 64)).resolves.toEqual({ ok: true });
await expect(parseJsonResponseWithLimit(response, 64)).resolves.toEqual({ ok: true });
});
it("rejects oversized JSON responses before parsing them", async () => {
@@ -66,7 +66,7 @@ describe("dev-runner watched snapshot", () => {
const fileSystem = {
existsSync: fs.existsSync,
readdirSync(target: fs.PathLike, options?: fs.ObjectEncodingOptions & { withFileTypes: true }) {
readdirSync(target: fs.PathLike, options: fs.ObjectEncodingOptions & { withFileTypes: true }) {
if (target === routesDir) {
fs.rmSync(routesDir, { recursive: true, force: true });
}
@@ -84,6 +84,8 @@ describe("dev-runner watched snapshot", () => {
const root = createTempRoot("paperclip-dev-runner-snapshot-diff-");
const missingPath = path.join(root, "server", "deleted.ts");
const fileSystem = {
existsSync: fs.existsSync,
readdirSync: fs.readdirSync,
statSync() {
const error = new Error("not a directory") as NodeJS.ErrnoException;
error.code = "ENOTDIR";
@@ -18,7 +18,7 @@ import {
writeDevServerRestartRequest,
} from "../dev-server-status.js";
const tempDirs = [];
const tempDirs: string[] = [];
function createTempStatusFile(payload: unknown) {
const dir = mkdtempSync(path.join(os.tmpdir(), "paperclip-dev-status-"));
@@ -30,6 +30,7 @@ function createMemoryReaperStore(): AdapterAuthReaperStore & {
seed(input) {
const row: AdapterAuthSessionRow = {
id: input.id,
publicSessionId: input.publicSessionId ?? randomUUID(),
companyId: input.companyId ?? randomUUID(),
environmentId: input.environmentId ?? randomUUID(),
adapterType: input.adapterType ?? ADAPTER_TYPE,
@@ -40,6 +41,7 @@ function createMemoryReaperStore(): AdapterAuthReaperStore & {
promotionExpiresAt: input.promotionExpiresAt ?? null,
finishedAt: input.finishedAt ?? null,
failureReason: input.failureReason ?? null,
resultClaim: input.resultClaim ?? null,
};
rows.set(row.id, row);
return row;
@@ -218,6 +218,7 @@ function createMemoryStore(): AdapterAuthSessionStore & {
promotionExpiresAt: null,
finishedAt: null,
failureReason: null,
resultClaim: null,
});
},
async recordLeaseAcquired(input) {
@@ -1413,7 +1414,7 @@ describeEmbeddedPostgres("device login service concurrency (embedded postgres)",
beforeAll(async () => {
const started = await startEmbeddedPostgresTestDatabase("device-login");
stopDb = started.stop;
stopDb = started.cleanup;
db = createDb(started.connectionString);
});
@@ -2039,7 +2040,7 @@ describeEmbeddedPostgres("device login service concurrency (embedded postgres)",
// The start ttl read: `session.expiresAt` reflects the profile's
// `timeoutMs`, not the fixed `DEVICE_LOGIN_TIMEOUT_MS`.
const expectedExpiresAt = Date.now() + customTimeoutMs;
expect(Math.abs(new Date(started.session.expiresAt).getTime() - expectedExpiresAt)).toBeLessThan(
expect(Math.abs(new Date(started.session.expiresAt!).getTime() - expectedExpiresAt)).toBeLessThan(
5000,
);
@@ -37,12 +37,12 @@ const mockIssueReferenceService = vi.hoisted(() => ({
syncIssue: vi.fn(async () => undefined),
}));
const mockHeartbeatService = vi.hoisted(() => ({
wakeup: vi.fn(async () => undefined),
wakeup: vi.fn(async (): Promise<{ id: string } | undefined> => undefined),
reportRunActivity: vi.fn(async () => undefined),
}));
const mockIssueThreadInteractionService = vi.hoisted(() => ({
expireRequestConfirmationsSupersededByComment: vi.fn(async () => []),
expireStaleRequestConfirmationsForIssueDocument: vi.fn(async () => []),
expireStaleRequestConfirmationsForIssueDocument: vi.fn(async (): Promise<Array<{ id: string; kind: string; status: string }>> => []),
}));
const mockLogActivity = vi.hoisted(() => vi.fn(async () => undefined));
@@ -22,7 +22,7 @@ describe("Dot onboarding with an operator-issued pairing capability", () => {
}, 120000);
beforeEach(async () => { await instanceSettingsService(db).updateExperimental({ enablePublicMcp: true, enableOpenAiDot: true, enableNativeRunner: false }); });
afterAll(async () => { await temp?.cleanup(); });
async function fixture(authorizationOrigin?: string, lifecycleState = "ready") {
async function fixture(authorizationOrigin?: string, lifecycleState: NonNullable<typeof agents.$inferInsert["lifecycleState"]> = "ready") {
const userId = randomUUID();
await db.insert(authUsers).values({ id: userId, name: "Operator", email: userId + "@example.test", createdAt: new Date(), updatedAt: new Date() });
const [company] = await db.insert(companies).values({ name: "Dot onboarding", issuePrefix: "DO" + randomBytes(3).toString("hex") }).returning();
@@ -64,7 +64,7 @@ describe("Dot onboarding with an operator-issued pairing capability", () => {
expect(revoked.lifecycleVersion).toBe(paired.lifecycleVersion + 1);
await expect(f.oauth.authenticate(tokens.access_token)).rejects.toThrow();
});
it.each(["preparing", "verifying"])("allows Dot setup during %s without allowing task access", async (state) => {
it.each(["preparing", "verifying"] as const)("allows Dot setup during %s without allowing task access", async (state) => {
const f = await fixture(undefined, state);
const tokens = await connect(f);
const principal = await f.oauth.authenticate(tokens.access_token);
+11 -5
View File
@@ -24,7 +24,8 @@ import { startEmbeddedPostgresTestDatabase } from "./helpers/embedded-postgres.j
import { createPublicMcpOAuth, DEVICE_GRANT } from "../services/public-mcp/oauth.js";
import { createPublicMcpExecutor } from "../services/public-mcp/capabilities.js";
import { publicMcpIngressRoutes, publicMcpManagementRoutes } from "../routes/public-mcp.js";
import { createPublicMcpEvents, type EventFetch } from "../services/public-mcp/events.js";
import { createPublicMcpEvents } from "../services/public-mcp/events.js";
import type { EventFetch } from "../services/public-mcp/event-webhooks.js";
import { instanceSettingsService } from "../services/instance-settings.js";
import { canonicalNativeRuntimeContextDigest, type StrictCompletionContractInput } from "../vendor/paperclip-runner/index.js";
import { createDotRunnerMcpTools, dotRunnerBroker } from "../services/dot-runner-broker.js";
@@ -46,6 +47,11 @@ import { createReleaseIssueExecution } from "../modules/wake-queue/application/u
const externalUrl = resolveExternalTestDatabaseUrl("PAPERCLIP_DOT_RUNNER_TEST_DATABASE_URL");
function uploadGrantUrl(grant: { url: string } | { attachment: unknown }) {
if (!("url" in grant)) throw new Error("Expected an upload grant");
return new URL(grant.url);
}
describe("durable Dot Runner integration", () => {
let temporary: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>>;
let db: ReturnType<typeof createDb>;
@@ -264,7 +270,7 @@ describe("durable Dot Runner integration", () => {
const body = Buffer.from("Authenticated concurrent deliverable"), uploadRequestId = randomUUID(), operationId = randomUUID();
const upload = await transfers.getUploadUrl(f.principal, { companyId: f.company.id, taskId: issue!.id, requestId: uploadRequestId,
filename: "concurrent.txt", contentType: "text/plain", byteSize: body.length, sha256: createHash("sha256").update(body).digest("hex") }, assignment.id);
const url = new URL(String(upload.url)), api = express(); api.use(transfers.router);
const url = uploadGrantUrl(upload), api = express(); api.use(transfers.router);
let uploading: Promise<request.Response> | undefined, uploaded: request.Response | undefined, uploadError: unknown;
try {
await db.transaction(async tx => {
@@ -961,7 +967,7 @@ describe("durable Dot Runner integration", () => {
const [assignment] = await db.select().from(dotRunnerAssignments).where(eq(dotRunnerAssignments.runId, run!.id));
if (provider === "openai_dot") await f.events.tick();
expect(JSON.stringify(f.received)).not.toContain("17 + 25");
const work = await f.broker.read(f.principal, assignment!.id);
const work = await f.broker.read(f.principal, assignment!.id) as Awaited<ReturnType<typeof f.broker.read>> & { tools: unknown };
expect(work.accounting).toEqual({ usage: null, cost: null });
expect(work.status).toBe("offered");
expect(observed.filter(event => event.eventType === "turn.started")).toHaveLength(0);
@@ -990,7 +996,7 @@ describe("durable Dot Runner integration", () => {
const body = Buffer.from("Grok arithmetic file: 42\n"), uploadId = randomUUID();
const args = { companyId: f.company.id, taskId: issue!.id, requestId: uploadId, filename: "report.txt", contentType: "text/plain", byteSize: body.length, sha256: createHash("sha256").update(body).digest("hex") };
const grant = await transfers.getUploadUrl(f.principal, args, assignment!.id);
const url = new URL(String(grant.url));
const url = uploadGrantUrl(grant);
if (revokeDuringUpload) {
const uploading = request(api).put(url.pathname + url.search).set("Content-Type", "text/plain").send(body)
.then(response => response);
@@ -1077,7 +1083,7 @@ describe("durable Dot Runner integration", () => {
await expect(transfers.getUploadUrl(f.principal, { ...args, taskId: randomUUID() }, assignment!.id)).rejects.toThrow();
const download = new URL(String((await transfers.getDownloadUrl(f.principal, uploaded.body.attachment.id, assignment!.id)).url));
expect((await request(api).get(download.pathname + download.search)).status).toBe(200);
const pendingUpload = new URL(String((await transfers.getUploadUrl(f.principal, { ...args, requestId: randomUUID() }, assignment!.id)).url));
const pendingUpload = uploadGrantUrl(await transfers.getUploadUrl(f.principal, { ...args, requestId: randomUUID() }, assignment!.id));
await db.update(issues).set({ workMode: "ask" }).where(eq(issues.id, issue!.id));
await expect(transfers.getUploadUrl(f.principal, { ...args, requestId: randomUUID() }, assignment!.id)).rejects.toThrow("work mode");
expect((await request(api).put(pendingUpload.pathname + pendingUpload.search).set("Content-Type", "text/plain").send(body)).status).toBeGreaterThanOrEqual(400);
@@ -7,6 +7,7 @@ import {
DUPLEX_SPAN_CHANNEL_OPEN,
DUPLEX_SPAN_REQUEST,
DUPLEX_TRANSPORT_EVENT,
type DuplexObservabilityDimensions,
} from "@paperclipai/adapter-utils/duplex-observability";
import { runWithRuntimeParent } from "@paperclipai/adapter-utils/acpx-engine/startup-timing";
import {
@@ -180,7 +181,7 @@ describe("createHostDuplexObservabilityRecorder", () => {
});
it("forwards the transport event with its name and dimensions", () => {
const events: Array<{ name: string; dimensions: Record<string, unknown> }> =
const events: Array<{ name: string; dimensions: DuplexObservabilityDimensions }> =
[];
const recorder = createHostDuplexObservabilityRecorder({
tracer: createRecordingTracer().tracer,
@@ -183,7 +183,7 @@ describe("durable inbound chat scheduler receipts", () => {
wakeCommentId: durableChatRequest.commentId,
durableChatRequest,
rethrowOnError: true,
});
}) as Promise<Awaited<ReturnType<typeof heartbeat.wakeup>>> | undefined;
return {
companyId,
agentId,
@@ -217,7 +217,7 @@ describe("AgentMail durable email pipeline", () => {
});
async function fixture(mode: "websocket" | "webhook" = "webhook", storage?: StorageService,
beforeSetup?: (input: { companyId: string; fetcher: typeof fetch; wakeup: ReturnType<typeof vi.fn> }) => Promise<void>,
beforeSetup?: (input: { companyId: string; fetcher: typeof fetch; wakeup: Parameters<typeof emailChannelService>[1]["heartbeat"]["wakeup"] }) => Promise<void>,
) {
const companyId = randomUUID(),
agentId = randomUUID(),
@@ -344,8 +344,8 @@ describe("AgentMail durable email pipeline", () => {
{ userId: "email-board" },
);
function message(
id = randomUUID(),
thread = randomUUID(),
id: string = randomUUID(),
thread: string = randomUUID(),
extra: Partial<AgentmailMessage> = {},
) {
return agentmailMessageSchema.parse({
@@ -1318,7 +1318,7 @@ describe("AgentMail durable email pipeline", () => {
await db.insert(principalPermissionGrants).values({ companyId: f.companyId, principalType: "user",
principalId: actor.userId, permissionKey: "tools:manage_connections" });
expect(await accessService(db).hasPermission(f.companyId, "user", actor.userId, "tools:manage_connections")).toBe(true);
expect(await accessService(db).hasPermission(f.companyId, "user", actor.userId, "agent_config:update")).toBe(false);
expect(await accessService(db).hasPermission(f.companyId, "user", actor.userId, "agents:configure")).toBe(false);
const requestId = randomUUID();
const svc = emailConnectionService(db, f.fetcher);
const connection = await svc.connect(f.companyId, { apiKey: "test-key", grantKind: "organization",
+2 -1
View File
@@ -14,8 +14,9 @@ function fixture() {
vi.useFakeTimers();
const select = vi.fn(() => ({ from: () => ({ where: async () => [] }) }));
const execute = vi.fn(async () => [{ xid: "42", status: "aborted" }]);
type FixtureTx = { execute: typeof execute; transaction: <T>(callback: (tx: FixtureTx) => Promise<T>) => Promise<T> };
const db = installDatabaseWorkSignals({ select, execute,
async transaction(callback: (tx: any) => Promise<any>) {
async transaction<T>(callback: (tx: FixtureTx) => Promise<T>): Promise<T> {
return callback({ execute, transaction: db.transaction });
},
});
@@ -186,11 +186,11 @@ function createSession(overrides: Record<string, unknown> = {}) {
};
}
function createApp(actor: Record<string, unknown>) {
function createApp(actor: Express.Request["actor"]) {
const app = express();
app.use(express.json());
app.use((req, _res, next) => {
(req as typeof req & { actor: Record<string, unknown> }).actor = actor;
req.actor = actor;
next();
});
app.use("/api", environmentRoutes({} as never));
@@ -202,7 +202,7 @@ function createApp(actor: Record<string, unknown>) {
return app;
}
function boardActor(overrides: Record<string, unknown> = {}) {
function boardActor(overrides: Partial<Express.Request["actor"]> = {}): Express.Request["actor"] {
return {
type: "board",
userId: "user-1",
@@ -213,7 +213,7 @@ function boardActor(overrides: Record<string, unknown> = {}) {
};
}
function agentActor() {
function agentActor(): Express.Request["actor"] {
return {
type: "agent",
agentId: "agent-1",
@@ -1,7 +1,7 @@
import { EventEmitter } from "node:events";
import { createServer, type Server as HttpServer } from "node:http";
import { createRequire } from "node:module";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from "vitest";
vi.mock("../services/environment-custom-images.js", () => ({
environmentCustomImageService: vi.fn(() => {
@@ -78,6 +78,10 @@ function futureDate(minutes = 60) {
return new Date(Date.now() + minutes * 60 * 1000);
}
type CustomImageTerminalService = NonNullable<
NonNullable<Parameters<typeof setupEnvironmentCustomImageTerminalWebSocketServer>[2]>["customImageService"]
>;
function createSession(overrides: Record<string, unknown> = {}) {
return {
id: "session-1",
@@ -178,8 +182,8 @@ describe("custom image terminal websocket bridge", () => {
let connectionRegistry: EnvironmentCustomImageTerminalConnectionRegistry;
let fakeShell: FakeSshShell;
let customImages: {
getSessionById: ReturnType<typeof vi.fn>;
refreshSetupSession: ReturnType<typeof vi.fn>;
getSessionById: Mock<CustomImageTerminalService["getSessionById"]>;
refreshSetupSession: Mock<CustomImageTerminalService["refreshSetupSession"]>;
};
let connector: EnvironmentCustomImageSshConnector & { connect: ReturnType<typeof vi.fn> };
@@ -14,6 +14,7 @@ import {
environments,
plugins,
} from "@paperclipai/db";
import type { PaperclipPluginManifestV1 } from "@paperclipai/shared";
import type { PluginWorkerManager } from "../services/plugin-worker-manager.js";
import {
environmentCustomImageService,
@@ -47,6 +48,8 @@ function pluginManifest() {
apiVersion: 1,
version: "0.1.0",
displayName: "Fake Sandbox Provider",
description: "Test fake sandbox provider",
author: "Paperclip",
categories: ["automation"],
capabilities: ["environment.drivers.register"],
entrypoints: { worker: "./dist/worker.js" },
@@ -68,7 +71,7 @@ function pluginManifest() {
configSchema: { type: "object" },
},
],
} as const;
} satisfies PaperclipPluginManifestV1;
}
const ALL_CUSTOM_IMAGE_WORKER_METHODS = [
@@ -1229,6 +1232,8 @@ function secretPluginManifest() {
apiVersion: 1,
version: "0.1.0",
displayName: "Fake Secret Sandbox Provider",
description: "Test fake sandbox provider",
author: "Paperclip",
categories: ["automation"],
capabilities: ["environment.drivers.register"],
entrypoints: { worker: "./dist/worker.js" },
@@ -1259,7 +1264,7 @@ function secretPluginManifest() {
},
},
],
} as const;
} satisfies PaperclipPluginManifestV1;
}
function invalidIdentityPluginManifest() {
@@ -1268,6 +1273,8 @@ function invalidIdentityPluginManifest() {
apiVersion: 1,
version: "0.1.0",
displayName: "Fake Invalid Identity Sandbox Provider",
description: "Test fake sandbox provider",
author: "Paperclip",
categories: ["automation"],
capabilities: ["environment.drivers.register"],
entrypoints: { worker: "./dist/worker.js" },
@@ -1287,7 +1294,7 @@ function invalidIdentityPluginManifest() {
configSchema: { type: "object" },
},
],
} as const;
} satisfies PaperclipPluginManifestV1;
}
function prototypeKeyIdentityPluginManifest() {
@@ -1296,6 +1303,8 @@ function prototypeKeyIdentityPluginManifest() {
apiVersion: 1,
version: "0.1.0",
displayName: "Fake Prototype Key Sandbox Provider",
description: "Test fake sandbox provider",
author: "Paperclip",
categories: ["automation"],
capabilities: ["environment.drivers.register"],
entrypoints: { worker: "./dist/worker.js" },
@@ -1316,7 +1325,7 @@ function prototypeKeyIdentityPluginManifest() {
configSchema: { type: "object" },
},
],
} as const;
} satisfies PaperclipPluginManifestV1;
}
describeEmbeddedPostgres("environmentCustomImageService relink", () => {
@@ -131,7 +131,7 @@ describe("sandbox driver duplex channel wiring", () => {
const channel = await service.openDuplexChannel({
environment: SANDBOX_ENVIRONMENT,
lease: PLUGIN_LEASE,
command: "bridge-callback",
command: ["bridge-callback"],
});
// The driver resolves the worker by the pinned plugin id and passes the same
@@ -142,7 +142,7 @@ describe("sandbox driver duplex channel wiring", () => {
companyId: "company-1",
environmentId: "env-1",
providerLeaseId: "provider-lease-1",
command: "bridge-callback",
command: ["bridge-callback"],
});
// write / stop / close map to write / kill / close on the host session.
@@ -185,7 +185,7 @@ describe("sandbox driver duplex channel wiring", () => {
service.openDuplexChannel({
environment: SANDBOX_ENVIRONMENT,
lease: nonPluginLease,
command: "bridge-callback",
command: ["bridge-callback"],
}),
).rejects.toThrow(DUPLEX_DENIED);
expect(worker.openDuplexChannel).not.toHaveBeenCalled();
@@ -210,7 +210,7 @@ describe("EnvironmentRuntimeService.openDuplexChannel capability gate", () => {
service.openDuplexChannel({
environment: SANDBOX_ENVIRONMENT,
lease: PLUGIN_LEASE,
command: "bridge-callback",
command: ["bridge-callback"],
}),
).rejects.toThrow(DUPLEX_DENIED);
expect(worker.openDuplexChannel).not.toHaveBeenCalled();
@@ -228,7 +228,7 @@ describe("EnvironmentRuntimeService.openDuplexChannel capability gate", () => {
service.openDuplexChannel({
environment: SANDBOX_ENVIRONMENT,
lease: PLUGIN_LEASE,
command: "bridge-callback",
command: ["bridge-callback"],
}),
).rejects.toThrow(DUPLEX_DENIED);
expect(worker.openDuplexChannel).not.toHaveBeenCalled();
@@ -253,7 +253,7 @@ describe("EnvironmentRuntimeService.openDuplexChannel capability gate", () => {
service.openDuplexChannel({
environment: SANDBOX_ENVIRONMENT,
lease: { ...PLUGIN_LEASE, metadata: { ...PLUGIN_LEASE.metadata, driver: "sandbox" } },
command: "bridge-callback",
command: ["bridge-callback"],
}),
).rejects.toThrow(DUPLEX_DENIED);
expect(openDuplexChannel).not.toHaveBeenCalled();
@@ -271,7 +271,7 @@ describe("EnvironmentRuntimeService.openDuplexChannel capability gate", () => {
service.openDuplexChannel({
environment: SANDBOX_ENVIRONMENT,
lease: PLUGIN_LEASE,
command: "bridge-callback",
command: ["bridge-callback"],
}),
).rejects.toThrow(DUPLEX_DENIED);
expect(worker.openDuplexChannel).not.toHaveBeenCalled();
@@ -353,11 +353,11 @@ describe("inline sandbox runner duplex capability gate", () => {
const { runner, openDuplexChannel } = await buildSandboxRunner({ snapshot: DUPLEX_GRANT });
expect(runner?.openDuplexChannel).toBeDefined();
await runner!.openDuplexChannel!({ command: "bridge-callback" });
await runner!.openDuplexChannel!({ command: ["bridge-callback"] });
expect(openDuplexChannel).toHaveBeenCalledWith({
environment: expect.objectContaining({ id: "env-1" }),
lease: expect.objectContaining({ id: "lease-1" }),
command: "bridge-callback",
command: ["bridge-callback"],
});
});
Loaded 100 of 345 files, more files were not shown because too many files have changed in this diff. Show more