mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 19:28:46 +01:00
merge: reconcile published native prerequisite and current master
Preserve canonical startup ownership, scoped evidence, current Storybook IDs, and the Codex form controls while carrying Claude effort and immutable source bindings. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
48ffca9438
415 files changed
+22501
-15340
No files matched your search
@@ -5,7 +5,6 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("node:child_process", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("node:child_process")>();
|
||||
// Execute the authored remote command locally, through both subprocess paths.
|
||||
return {
|
||||
...actual,
|
||||
execFile: vi.fn((file: string, args: string[], options: object, callback: Parameters<typeof actual.execFile>[3]) => {
|
||||
|
||||
@@ -68,8 +68,6 @@ export function createSshCommandManagedRuntimeRunner(input: {
|
||||
const envPrefix = envEntries.length > 0
|
||||
? `env ${envEntries.map(([key, value]) => shellQuote(`${key}=${value}`)).join(" ")} `
|
||||
: "";
|
||||
// Keep sh -c's command name and positional arguments intact. Flattening
|
||||
// its script loses $0/$1/... and breaks native runner launch/identity.
|
||||
const commandScript = `exec ${envPrefix}${[shellQuote(command), ...args.map((arg) => shellQuote(arg))].join(" ")}`;
|
||||
const remoteCommand = `cd ${shellQuote(cwd)} && ${commandScript}`;
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
CREATE TABLE IF NOT EXISTS "chat_slack_manager_grants" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"company_id" uuid NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"slack_user_id" text NOT NULL,
|
||||
"workspace_id" text NOT NULL,
|
||||
"workspace_name" text NOT NULL,
|
||||
"manager_app_id" text NOT NULL,
|
||||
"status" text DEFAULT 'active' NOT NULL,
|
||||
"access_secret_id" uuid,
|
||||
"refresh_secret_id" uuid,
|
||||
"expires_at" timestamp with time zone,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "chat_slack_manager_grants_status_check" CHECK ("chat_slack_manager_grants"."status" in ('active', 'revoked', 'reauthorize'))
|
||||
);
|
||||
|
||||
--> statement-breakpoint
|
||||
|
||||
ALTER TABLE "chat_slack_registrations" ADD COLUMN IF NOT EXISTS "manager_grant_id" uuid;
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "chat_slack_manager_grants" ADD CONSTRAINT "chat_slack_manager_grants_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
--> statement-breakpoint
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "chat_slack_manager_grants_company_id_idx" ON "chat_slack_manager_grants" USING btree ("company_id","id");
|
||||
--> statement-breakpoint
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "chat_slack_manager_grants_owner_idx" ON "chat_slack_manager_grants" USING btree ("company_id","user_id","manager_app_id","workspace_id");
|
||||
--> statement-breakpoint
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "chat_slack_registrations" ADD CONSTRAINT "chat_slack_registrations_company_id_manager_grant_id_chat_slack_manager_grants_company_id_id_fk" FOREIGN KEY ("company_id","manager_grant_id") REFERENCES "public"."chat_slack_manager_grants"("company_id","id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "chat_slack_manager_grants" ADD COLUMN IF NOT EXISTS "revision" integer DEFAULT 0 NOT NULL;
|
||||
--> statement-breakpoint
|
||||
|
||||
ALTER TABLE "chat_slack_manager_grants" ADD COLUMN IF NOT EXISTS "rate_limits" jsonb DEFAULT '{}'::jsonb NOT NULL;
|
||||
+817
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "0a93bc99-bf0c-4ea2-ab27-6043b6746cd5",
|
||||
"prevId": "76861d12-d9a9-4f5f-bcbc-0fb06c0addbc",
|
||||
"id": "18c156f2-8344-4282-98b7-f702f0035c81",
|
||||
"prevId": "39d82f2c-8fd5-4606-84df-f51880dbd6a7",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
@@ -416,6 +416,12 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"fast_response_request_id": {
|
||||
"name": "fast_response_request_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"agent_id": {
|
||||
"name": "agent_id",
|
||||
"type": "uuid",
|
||||
@@ -462,6 +468,27 @@
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"budget_reservations_fast_response_idx": {
|
||||
"name": "budget_reservations_fast_response_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "fast_response_request_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"budget_reservations_decision_idx": {
|
||||
"name": "budget_reservations_decision_idx",
|
||||
"columns": [
|
||||
@@ -580,7 +607,7 @@
|
||||
"checkConstraints": {
|
||||
"budget_reservations_source_check": {
|
||||
"name": "budget_reservations_source_check",
|
||||
"value": "(\"budget_reservations\".\"run_id\" is not null)::int + (\"budget_reservations\".\"decision_invocation_id\" is not null)::int = 1"
|
||||
"value": "(\"budget_reservations\".\"run_id\" is not null)::int + (\"budget_reservations\".\"decision_invocation_id\" is not null)::int + (\"budget_reservations\".\"fast_response_request_id\" is not null)::int = 1"
|
||||
},
|
||||
"budget_reservations_state_check": {
|
||||
"name": "budget_reservations_state_check",
|
||||
@@ -3737,6 +3764,28 @@
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"agent_wakeup_requests_question_response_history_idx": {
|
||||
"name": "agent_wakeup_requests_question_response_history_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "idempotency_key",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"where": "\"agent_wakeup_requests\".\"idempotency_key\" LIKE 'question-response:%'",
|
||||
"concurrently": true,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"agent_wakeup_requests_connection_intent_delivery_idempotency_uq": {
|
||||
"name": "agent_wakeup_requests_connection_intent_delivery_idempotency_uq",
|
||||
"columns": [
|
||||
@@ -12384,6 +12433,189 @@
|
||||
},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.chat_slack_manager_grants": {
|
||||
"name": "chat_slack_manager_grants",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slack_user_id": {
|
||||
"name": "slack_user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"workspace_id": {
|
||||
"name": "workspace_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"workspace_name": {
|
||||
"name": "workspace_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"manager_app_id": {
|
||||
"name": "manager_app_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'active'"
|
||||
},
|
||||
"access_secret_id": {
|
||||
"name": "access_secret_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"refresh_secret_id": {
|
||||
"name": "refresh_secret_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"revision": {
|
||||
"name": "revision",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"rate_limits": {
|
||||
"name": "rate_limits",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'::jsonb"
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_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()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"chat_slack_manager_grants_company_id_idx": {
|
||||
"name": "chat_slack_manager_grants_company_id_idx",
|
||||
"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": {}
|
||||
},
|
||||
"chat_slack_manager_grants_owner_idx": {
|
||||
"name": "chat_slack_manager_grants_owner_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "manager_app_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "workspace_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"chat_slack_manager_grants_company_id_companies_id_fk": {
|
||||
"name": "chat_slack_manager_grants_company_id_companies_id_fk",
|
||||
"tableFrom": "chat_slack_manager_grants",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {
|
||||
"chat_slack_manager_grants_status_check": {
|
||||
"name": "chat_slack_manager_grants_status_check",
|
||||
"value": "\"chat_slack_manager_grants\".\"status\" in ('active', 'revoked', 'reauthorize')"
|
||||
}
|
||||
},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.chat_slack_registrations": {
|
||||
"name": "chat_slack_registrations",
|
||||
"schema": "",
|
||||
@@ -12400,6 +12632,12 @@
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"manager_grant_id": {
|
||||
"name": "manager_grant_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"request_id": {
|
||||
"name": "request_id",
|
||||
"type": "uuid",
|
||||
@@ -12490,6 +12728,21 @@
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"chat_slack_registrations_company_id_manager_grant_id_chat_slack_manager_grants_company_id_id_fk": {
|
||||
"name": "chat_slack_registrations_company_id_manager_grant_id_chat_slack_manager_grants_company_id_id_fk",
|
||||
"tableFrom": "chat_slack_registrations",
|
||||
"tableTo": "chat_slack_manager_grants",
|
||||
"columnsFrom": [
|
||||
"company_id",
|
||||
"manager_grant_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"company_id",
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"chat_slack_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk": {
|
||||
"name": "chat_slack_registrations_company_id_endpoint_id_chat_endpoints_company_id_id_fk",
|
||||
"tableFrom": "chat_slack_registrations",
|
||||
@@ -16725,6 +16978,19 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"package_mode": {
|
||||
"name": "package_mode",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'skills'"
|
||||
},
|
||||
"repository_package": {
|
||||
"name": "repository_package",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"excluded_folders": {
|
||||
"name": "excluded_folders",
|
||||
"type": "jsonb",
|
||||
@@ -17052,6 +17318,87 @@
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.company_skill_repository_snapshots": {
|
||||
"name": "company_skill_repository_snapshots",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"content_hash": {
|
||||
"name": "content_hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"file_inventory": {
|
||||
"name": "file_inventory",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"company_skill_repository_snapshots_content_idx": {
|
||||
"name": "company_skill_repository_snapshots_content_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "content_hash",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"company_skill_repository_snapshots_company_id_companies_id_fk": {
|
||||
"name": "company_skill_repository_snapshots_company_id_companies_id_fk",
|
||||
"tableFrom": "company_skill_repository_snapshots",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.company_skill_stars": {
|
||||
"name": "company_skill_stars",
|
||||
"schema": "",
|
||||
@@ -17906,6 +18253,18 @@
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"repository_snapshot_id": {
|
||||
"name": "repository_snapshot_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"repository_skill_path": {
|
||||
"name": "repository_skill_path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"revision_number": {
|
||||
"name": "revision_number",
|
||||
"type": "integer",
|
||||
@@ -18062,6 +18421,19 @@
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"company_skill_versions_repository_snapshot_id_company_skill_repository_snapshots_id_fk": {
|
||||
"name": "company_skill_versions_repository_snapshot_id_company_skill_repository_snapshots_id_fk",
|
||||
"tableFrom": "company_skill_versions",
|
||||
"tableTo": "company_skill_repository_snapshots",
|
||||
"columnsFrom": [
|
||||
"repository_snapshot_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "restrict",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"company_skill_versions_author_agent_id_agents_id_fk": {
|
||||
"name": "company_skill_versions_author_agent_id_agents_id_fk",
|
||||
"tableFrom": "company_skill_versions",
|
||||
@@ -19576,6 +19948,34 @@
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"cost_events_data_quality_idx": {
|
||||
"name": "cost_events_data_quality_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "occurred_at",
|
||||
"isExpression": false,
|
||||
"asc": false,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"where": "\"cost_events\".\"billing_type\" = 'unknown' or (\"cost_events\".\"cost_status\" = 'unpriced' and \"cost_events\".\"billing_type\" <> 'subscription_included')",
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"cost_events_company_occurred_idx": {
|
||||
"name": "cost_events_company_occurred_idx",
|
||||
"columns": [
|
||||
@@ -19826,7 +20226,7 @@
|
||||
"checkConstraints": {
|
||||
"cost_events_usage_kind_check": {
|
||||
"name": "cost_events_usage_kind_check",
|
||||
"value": "\"cost_events\".\"usage_kind\" = 'decision' or (\"cost_events\".\"usage_kind\" = 'agent' and \"cost_events\".\"agent_id\" is not null)"
|
||||
"value": "\"cost_events\".\"usage_kind\" in ('decision', 'fast_response') or (\"cost_events\".\"usage_kind\" = 'agent' and \"cost_events\".\"agent_id\" is not null)"
|
||||
},
|
||||
"cost_events_nonnegative_amounts": {
|
||||
"name": "cost_events_nonnegative_amounts",
|
||||
@@ -27491,6 +27891,378 @@
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.company_fast_responses": {
|
||||
"name": "company_fast_responses",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"grant_id": {
|
||||
"name": "grant_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"model": {
|
||||
"name": "model",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"enabled": {
|
||||
"name": "enabled",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"allow_sponsored": {
|
||||
"name": "allow_sponsored",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": true
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"company_fast_responses_company_id_companies_id_fk": {
|
||||
"name": "company_fast_responses_company_id_companies_id_fk",
|
||||
"tableFrom": "company_fast_responses",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {
|
||||
"company_fast_responses_configured": {
|
||||
"name": "company_fast_responses_configured",
|
||||
"value": "(\"company_fast_responses\".\"connection_id\" is null) = (\"company_fast_responses\".\"grant_id\" is null) and (not \"company_fast_responses\".\"enabled\" or (\"company_fast_responses\".\"connection_id\" is not null and \"company_fast_responses\".\"model\" is not null))"
|
||||
}
|
||||
},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.fast_response_requests": {
|
||||
"name": "fast_response_requests",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"company_id": {
|
||||
"name": "company_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"source_key": {
|
||||
"name": "source_key",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"source_comment_id": {
|
||||
"name": "source_comment_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"issue_id": {
|
||||
"name": "issue_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"project_id": {
|
||||
"name": "project_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"agent_id": {
|
||||
"name": "agent_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"responsible_user_id": {
|
||||
"name": "responsible_user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"sponsored": {
|
||||
"name": "sponsored",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"endpoint_id": {
|
||||
"name": "endpoint_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"conversation_id": {
|
||||
"name": "conversation_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"delivery_id": {
|
||||
"name": "delivery_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"session_generation": {
|
||||
"name": "session_generation",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'pending'"
|
||||
},
|
||||
"publication_status": {
|
||||
"name": "publication_status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'pending'"
|
||||
},
|
||||
"connection_id": {
|
||||
"name": "connection_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"grant_id": {
|
||||
"name": "grant_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"model": {
|
||||
"name": "model",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"provider_request_id": {
|
||||
"name": "provider_request_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"cost_event_id": {
|
||||
"name": "cost_event_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"error_code": {
|
||||
"name": "error_code",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"comment_id": {
|
||||
"name": "comment_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"accepted_at": {
|
||||
"name": "accepted_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"started_at": {
|
||||
"name": "started_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"finished_at": {
|
||||
"name": "finished_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"duration_ms": {
|
||||
"name": "duration_ms",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"input_tokens": {
|
||||
"name": "input_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"output_tokens": {
|
||||
"name": "output_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"fast_response_requests_source_idx": {
|
||||
"name": "fast_response_requests_source_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "source_key",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"fast_response_requests_history_idx": {
|
||||
"name": "fast_response_requests_history_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "accepted_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"fast_response_requests_work_idx": {
|
||||
"name": "fast_response_requests_work_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "expires_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"where": "\"fast_response_requests\".\"status\" in ('pending', 'running')",
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"fast_response_requests_company_id_companies_id_fk": {
|
||||
"name": "fast_response_requests_company_id_companies_id_fk",
|
||||
"tableFrom": "fast_response_requests",
|
||||
"tableTo": "companies",
|
||||
"columnsFrom": [
|
||||
"company_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"fast_response_requests_cost_event_id_cost_events_id_fk": {
|
||||
"name": "fast_response_requests_cost_event_id_cost_events_id_fk",
|
||||
"tableFrom": "fast_response_requests",
|
||||
"tableTo": "cost_events",
|
||||
"columnsFrom": [
|
||||
"cost_event_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {
|
||||
"fast_response_requests_status_check": {
|
||||
"name": "fast_response_requests_status_check",
|
||||
"value": "\"fast_response_requests\".\"status\" in ('pending', 'running', 'succeeded', 'failed', 'unknown', 'skipped')"
|
||||
}
|
||||
},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.feedback_exports": {
|
||||
"name": "feedback_exports",
|
||||
"schema": "",
|
||||
@@ -29938,6 +30710,34 @@
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"heartbeat_runs_cost_data_quality_idx": {
|
||||
"name": "heartbeat_runs_cost_data_quality_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "company_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "coalesce(\"finished_at\", \"created_at\") desc",
|
||||
"asc": true,
|
||||
"isExpression": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"where": "\"heartbeat_runs\".\"cost_accounting_pending\" = true and \"heartbeat_runs\".\"status\" in ('succeeded','failed','timed_out','cancelled','interrupted')",
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"heartbeat_runs_execution_status_delivery_idx": {
|
||||
"name": "heartbeat_runs_execution_status_delivery_idx",
|
||||
"columns": [
|
||||
@@ -32392,6 +33192,19 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"origin": {
|
||||
"name": "origin",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'comment'"
|
||||
},
|
||||
"fast_response_request_id": {
|
||||
"name": "fast_response_request_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"body": {
|
||||
"name": "body",
|
||||
"type": "text",
|
||||
+7
@@ -2332,6 +2332,13 @@
|
||||
"when": 1791807836288,
|
||||
"tag": "0334_rare_slipstream",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 335,
|
||||
"version": "7",
|
||||
"when": 1791832982875,
|
||||
"tag": "0335_absurd_bulldozer",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { check, integer, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
|
||||
/** Reusable personal management authorization; never a bot runtime credential. */
|
||||
export const chatSlackManagerGrants = pgTable("chat_slack_manager_grants", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id, { onDelete: "cascade" }),
|
||||
userId: text("user_id").notNull(),
|
||||
slackUserId: text("slack_user_id").notNull(),
|
||||
workspaceId: text("workspace_id").notNull(),
|
||||
workspaceName: text("workspace_name").notNull(),
|
||||
managerAppId: text("manager_app_id").notNull(),
|
||||
status: text("status").$type<"active" | "revoked" | "reauthorize">().notNull().default("active"),
|
||||
accessSecretId: uuid("access_secret_id"),
|
||||
refreshSecretId: uuid("refresh_secret_id"),
|
||||
revision: integer("revision").notNull().default(0),
|
||||
rateLimits: jsonb("rate_limits").$type<Record<string, string>>().notNull().default({}),
|
||||
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
}, t => [
|
||||
uniqueIndex("chat_slack_manager_grants_company_id_idx").on(t.companyId, t.id),
|
||||
uniqueIndex("chat_slack_manager_grants_owner_idx").on(t.companyId, t.userId, t.managerAppId, t.workspaceId),
|
||||
check("chat_slack_manager_grants_status_check", sql`${t.status} in ('active', 'revoked', 'reauthorize')`),
|
||||
]);
|
||||
@@ -1,12 +1,14 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { check, foreignKey, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
||||
import type { SlackRegistrationStatus } from "@paperclipai/shared";
|
||||
import { chatSlackManagerGrants } from "./chat_slack_manager_grants.js";
|
||||
import { chatEndpoints } from "./chat_channels.js";
|
||||
|
||||
/** App provisioning is distinct from installation and runtime credentials. No plaintext secrets. */
|
||||
export const chatSlackRegistrations = pgTable("chat_slack_registrations", {
|
||||
endpointId: uuid("endpoint_id").primaryKey(),
|
||||
companyId: uuid("company_id").notNull(),
|
||||
managerGrantId: uuid("manager_grant_id"),
|
||||
requestId: uuid("request_id").notNull(),
|
||||
status: text("status").$type<SlackRegistrationStatus>().notNull(),
|
||||
manifest: jsonb("manifest").$type<Record<string, unknown>>().notNull(),
|
||||
@@ -22,6 +24,7 @@ export const chatSlackRegistrations = pgTable("chat_slack_registrations", {
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
}, t => [
|
||||
foreignKey({ columns: [t.companyId, t.managerGrantId], foreignColumns: [chatSlackManagerGrants.companyId, chatSlackManagerGrants.id] }),
|
||||
foreignKey({ columns: [t.companyId, t.endpointId], foreignColumns: [chatEndpoints.companyId, chatEndpoints.id] }).onDelete("cascade"),
|
||||
check("chat_slack_registration_status_check", sql`${t.status} in ('creating', 'uncertain', 'failed', 'install', 'credentials_saved', 'configured', 'removed')`),
|
||||
]);
|
||||
@@ -243,6 +243,8 @@ export { chatVoiceReports } from "./chat_voice_reports.js";
|
||||
export { chatVoicePhoneLines, chatVoiceInboundCalls } from "./chat_voice_inbound.js";
|
||||
export * from "./decision_models.js";
|
||||
export { userCompanyPreferences } from "./user_company_preferences.js";
|
||||
|
||||
export { chatSlackManagerGrants } from "./chat_slack_manager_grants.js";
|
||||
export { aiSubscriptions, aiSubscriptionPrices, aiSubscriptionConnections } from "./ai_subscriptions.js";
|
||||
|
||||
export * from "./fast_responses.js";
|
||||
@@ -1,4 +1,3 @@
|
||||
// Assemble the four independently built release daemons before npm packing.
|
||||
// Usage: node packages/paperclip-runner/scripts/stage-release-runner-binaries.mjs MANIFEST.json
|
||||
// Manifest: {sourceRevision, platforms: {"darwin-arm64": {path, sha256}, ...}, remoteProviderPack: {path, sha256}}.
|
||||
import { createHash } from "node:crypto";
|
||||
@@ -31,8 +30,6 @@ function regularFile(path, maximumSize) {
|
||||
return readFileSync(path);
|
||||
}
|
||||
|
||||
// Production TypeScript contracts are built by the existing producer step.
|
||||
// Foreign artifacts are data: the assembler never executes another target.
|
||||
async function metadataContract() {
|
||||
const { PAPERCLIP_RUNNER_BUILD_METADATA } = await import("../dist/evals/build-metadata.js");
|
||||
const { parsePaperclipRunnerdBuildMetadata } = await import("../dist/evals/runnerd-artifact.js");
|
||||
@@ -112,16 +109,12 @@ async function main() {
|
||||
if (packBytes.length > 512 * 1024 || "sha256:" + createHash("sha256").update(packBytes).digest("hex") !== entry.sha256) throw Error("Remote provider-pack release manifest digest mismatch");
|
||||
packIdentity = verifyReleaseProviderPack(JSON.parse(packBytes), manifest.sourceRevision);
|
||||
} else if (daemonOnly) {
|
||||
// Preserve a qualified same-source Linux pack already materialized by the
|
||||
// normal producer. It never becomes a Mac provider-pack qualification.
|
||||
packIdentity = existingReleaseProviderPack(root, manifest.sourceRevision);
|
||||
}
|
||||
const platforms = {};
|
||||
for (const artifact of verified) {
|
||||
const relative = `${artifact.target}/paperclip-runnerd`, destination = join(root, "dist", "bin", relative);
|
||||
mkdirSync(dirname(destination), { recursive: true }); copyFileSync(artifact.source, destination); chmodSync(destination, 0o755);
|
||||
// Copied Mach-O inodes need a fresh ad-hoc signature on macOS. Record the
|
||||
// resulting packaged bytes, as well as the verified build artifact identity.
|
||||
if (process.platform === "darwin" && artifact.target.startsWith("darwin-")) {
|
||||
execFileSync("codesign", ["--force", "--sign", "-", destination], { stdio: "pipe", timeout: 30_000 });
|
||||
}
|
||||
@@ -131,7 +124,6 @@ async function main() {
|
||||
if (packBytes) {
|
||||
const destination = join(root, "dist", "remote-provider-packs", "linux-x64", "provider-pack.json");
|
||||
mkdirSync(dirname(destination), { recursive: true });
|
||||
// Replace the inode so repeat assembly can preserve a read-only manifest.
|
||||
const staged = `${destination}.staging-${process.pid}`;
|
||||
try {
|
||||
writeFileSync(staged, packBytes, { mode: 0o444, flag: "wx" });
|
||||
|
||||
@@ -32,8 +32,6 @@ export async function stageBuiltRunnerBinary(packageRoot) {
|
||||
const executable = process.platform === "win32" ? "paperclip-runnerd.exe" : "paperclip-runnerd";
|
||||
const source = path.join(packageRoot, "runner", "target", "release", executable);
|
||||
const binaryRoot = path.join(packageRoot, "dist", "bin");
|
||||
// Source builds are separate from the source-bound release targets and their
|
||||
// manifest. The dev supervisor selects this fresh output for its server.
|
||||
await stageRunnerBinary(source, path.join(binaryRoot, executable));
|
||||
}
|
||||
|
||||
|
||||
@@ -140,8 +140,6 @@ test("a source rebuild selects fresh dev bytes while preserving release binaries
|
||||
const copyHostFixture = async (input, output) => {
|
||||
await copyFile(input, output);
|
||||
if (process.platform === "darwin") {
|
||||
// System fixtures can be universal; Cargo release daemons are thin.
|
||||
// Extract only this host slice before exercising the native header gate.
|
||||
const slices = execFileSync("/usr/bin/lipo", ["-archs", input], { encoding: "utf8" }).trim().split(/\s+/);
|
||||
const architecture = slices.find(value => process.arch === "arm64" ? value.startsWith("arm64") : value === "x86_64");
|
||||
assert.ok(architecture);
|
||||
|
||||
@@ -31,8 +31,6 @@ vi.mock("smol-toml", async (importOriginal) => {
|
||||
...actual,
|
||||
parse: (...args: Parameters<typeof actual.parse>) => {
|
||||
const result = actual.parse(...args);
|
||||
// Exercise both table representations independently of the installed
|
||||
// parser version while retaining the real TOML values.
|
||||
setTablePrototypes(result);
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -92,8 +92,6 @@ export function trustCodexStartupRoot(codexHome: string, cwd: string): void {
|
||||
const path = join(codexHome, "config.toml");
|
||||
const source = existsSync(path) ? readFileSync(path, "utf8") : "";
|
||||
const config = parse(source);
|
||||
// Retain the parser's table prototypes so strict candidate comparison also
|
||||
// works with parsers that return null-prototype TOML tables.
|
||||
const projects = config.projects ?? parse("");
|
||||
if (
|
||||
typeof projects !== "object" ||
|
||||
|
||||
@@ -396,7 +396,6 @@ it("publishes spawned runner ownership before waiting for provider startup", asy
|
||||
});
|
||||
const opening = bundle.transport.request("thread/start", { cwd: tmpdir(), dynamicTools: codexSemanticToolSpecs() });
|
||||
try {
|
||||
// Same-tick recovery must not mutate an already admitted fresh startup.
|
||||
await expect(bundle.transport.request("thread/read", { threadId: "rejected-duplicate" }))
|
||||
.rejects.toThrow("PRP provider thread is already started");
|
||||
await vi.waitFor(() => expect(onSpawn).toHaveBeenCalledOnce());
|
||||
@@ -2223,7 +2222,6 @@ it("owns blocked Codex home preparation through driver abort without later regis
|
||||
markPreparationStarted();
|
||||
await preparationReleased;
|
||||
preparationContinuedAfterClose = closeSettled;
|
||||
// A synthetic, credential-free write makes the owned preparation visible.
|
||||
await mkdir(input.codexHome, { mode: 0o700 });
|
||||
await writeFile(join(input.codexHome, "preparation-marker"), "fixture preparation drained\n", { mode: 0o600 });
|
||||
});
|
||||
@@ -2264,7 +2262,6 @@ it("owns blocked Codex home preparation through driver abort without later regis
|
||||
.then(value => { openingSettled = true; return { value }; }, error => { openingSettled = true; return { error }; });
|
||||
try {
|
||||
await preparationStarted;
|
||||
// A rejected duplicate must not replace the first admitted startup owner.
|
||||
await expect(transport.request("thread/start", { cwd: root, dynamicTools: [] }))
|
||||
.rejects.toThrow("PRP provider thread is already started");
|
||||
controller.abort(cancelled);
|
||||
@@ -2863,8 +2860,6 @@ it.each(["stuck", "late-release", "late-release-error", "stuck-release", "cooper
|
||||
runtimeDirectory, provider: "codex", model: "gpt-test", timeoutMs: 1_000,
|
||||
environment: { PATH: "", OPENAI_API_KEY: "inert-test-key" },
|
||||
transportOptions: {
|
||||
// Registration and launch are external owners; hash real inert bytes
|
||||
// without executing a daemon, Codex, or an ambient developer binary.
|
||||
runnerBinary: resolve(import.meta.dirname, "../../package.json"),
|
||||
codexCommand: resolve(import.meta.dirname, "../../package.json"),
|
||||
sourceCodexHome: "", runnerProcessLauncher: launch,
|
||||
@@ -2895,8 +2890,6 @@ it.each(["stuck", "late-release", "late-release-error", "stuck-release", "cooper
|
||||
expect(cleanup).not.toHaveBeenCalled();
|
||||
expect((await stat(runtimeDirectory)).isDirectory()).toBe(true);
|
||||
if (mode === "late-release-error") expect((failure as AggregateError).errors).toContain(releaseError);
|
||||
// A registration that finishes after the bounded return still releases
|
||||
// its authority, without retroactively certifying or deleting state.
|
||||
returnRegistration(registered);
|
||||
finishRelease();
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
|
||||
@@ -1338,7 +1338,6 @@ export interface CapabilityRunnerdCodexTransportOptions {
|
||||
controlPlaneRegistration?: (
|
||||
authority: DurablePrpControlPlane,
|
||||
identity?: DurableRecoveryIdentity,
|
||||
/** Setup owners abort preparation before draining or releasing its result. */
|
||||
signal?: AbortSignal,
|
||||
) => Promise<{
|
||||
connectUrl?: string;
|
||||
@@ -4466,8 +4465,6 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
|
||||
}
|
||||
|
||||
#trackStartup<T>(start: () => Promise<T>): Promise<T> {
|
||||
// Admit one owner before invoking startup. A duplicate request's rejection
|
||||
// must never replace the preparation that close is responsible for joining.
|
||||
if (this.#startupOperation !== null) {
|
||||
return Promise.reject(new Error("PRP provider thread is already started"));
|
||||
}
|
||||
@@ -7108,8 +7105,6 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
|
||||
}
|
||||
await Promise.race([
|
||||
ready(), this.#failureSignal,
|
||||
// Readiness is a notification wait, not launch-material preparation.
|
||||
// Its route owner is released by close; joining it first would cycle.
|
||||
this.#closedSignal.then(() => { this.#assertStartupOpen(); }),
|
||||
]);
|
||||
this.#assertStartupOpen();
|
||||
@@ -7160,7 +7155,6 @@ export function createCapabilityRunnerdCodexTransport(
|
||||
export const createRunnerdCodexTransport =
|
||||
createCapabilityRunnerdCodexTransport;
|
||||
|
||||
/** A setup turn uses the same daemon/provider boundary as ordinary native runs. */
|
||||
export async function probeNativeRunnerEnvironment(options: {
|
||||
runtimeDirectory: string;
|
||||
provider: "codex";
|
||||
@@ -7168,7 +7162,6 @@ export async function probeNativeRunnerEnvironment(options: {
|
||||
reasoningEffort?: string;
|
||||
environment: NodeJS.ProcessEnv;
|
||||
timeoutMs?: number;
|
||||
/** The selected remote filesystem supplies this cwd; never inspect it on the controller. */
|
||||
workingDirectory?: string;
|
||||
transportOptions?: CapabilityRunnerdCodexTransportOptions;
|
||||
onCodexCredentialRefresh?: (filename: string) => Promise<void>;
|
||||
@@ -7189,9 +7182,6 @@ export async function probeNativeRunnerEnvironment(options: {
|
||||
let registrationOperation: Promise<unknown> | undefined;
|
||||
let registrationCleanupConfirmed = true;
|
||||
const sourceCodexHome = options.transportOptions?.sourceCodexHome ?? resolveSourceCodexHome(environment);
|
||||
// Never let an unbound home or an external-sandbox flag admit host state or
|
||||
// weaken this read-only probe. The normal materializer copies bound auth and
|
||||
// the managed provider projection into a new daemon-owned Codex home.
|
||||
delete environment.PAPERCLIP_RUNNER_EXTERNAL_SANDBOX;
|
||||
environment.HOME = resolve(remoteFilesystemRoot ?? options.runtimeDirectory, "probe-home");
|
||||
environment.CODEX_HOME = resolve(remoteFilesystemRoot ?? options.runtimeDirectory, "codex-home");
|
||||
@@ -7310,8 +7300,6 @@ export async function probeNativeRunnerEnvironment(options: {
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
controller.abort(failure ?? new Error("Native provider hello probe complete."));
|
||||
// Preserve private recovery state if the production close cannot confirm
|
||||
// teardown; callers remove it only after an authoritative receipt.
|
||||
const teardownErrors: unknown[] = [];
|
||||
const teardownDeadline = Date.now() + 10_000;
|
||||
const awaitTeardown = async (pending: Promise<unknown>, detail: string): Promise<void> => {
|
||||
|
||||
@@ -441,8 +441,6 @@ beforeEach(() => {
|
||||
"div",
|
||||
{ key: routine.key },
|
||||
createElement("span", null, routine.title),
|
||||
createElement("button", null, "Run now"),
|
||||
createElement("button", { role: "switch" }, "On"),
|
||||
routine.href ? createElement("a", { href: `/PAP${routine.href}` }, "Configure") : null,
|
||||
routine.defaultDrift?.changedFields.length
|
||||
? createElement("span", null, `Plugin defaults changed: ${routine.defaultDrift.changedFields.join(", ")}`)
|
||||
@@ -656,6 +654,46 @@ function mockPersistedWikiSpace(harness: ReturnType<typeof createTestHarness>, s
|
||||
};
|
||||
}
|
||||
|
||||
function renderWikiPage(): string {
|
||||
return renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
}
|
||||
|
||||
function captureWikiWrites(harness: ReturnType<typeof createTestHarness>) {
|
||||
const writes: Array<{ path: string; contents: string }> = [];
|
||||
harness.ctx.localFolders.writeTextAtomic = async (_companyId, _folderKey, relativePath, contents) => {
|
||||
writes.push({ path: relativePath, contents });
|
||||
return harness.ctx.localFolders.status(COMPANY_ID, "wiki-root");
|
||||
};
|
||||
return writes;
|
||||
}
|
||||
|
||||
function managedSpaceRow(created: {
|
||||
space: { id: string; companyId: string; wikiId: string; slug: string; displayName: string; pathPrefix: string | null };
|
||||
}): Record<string, unknown> {
|
||||
return {
|
||||
id: created.space.id,
|
||||
company_id: created.space.companyId,
|
||||
wiki_id: created.space.wikiId,
|
||||
slug: created.space.slug,
|
||||
display_name: created.space.displayName,
|
||||
space_type: "local_folder",
|
||||
folder_mode: "managed_subfolder",
|
||||
root_folder_key: "wiki-root",
|
||||
path_prefix: created.space.pathPrefix,
|
||||
configured_root_path: null,
|
||||
access_scope: "shared",
|
||||
owner_user_id: null,
|
||||
owner_agent_id: null,
|
||||
team_key: null,
|
||||
settings: {},
|
||||
status: "active",
|
||||
created_at: null,
|
||||
updated_at: null,
|
||||
};
|
||||
}
|
||||
|
||||
describe("LLM Wiki plugin scaffold", () => {
|
||||
it("declares standalone plugin surfaces without core wiki coupling", () => {
|
||||
expect(manifest.id).toBe("paperclipai.plugin-llm-wiki");
|
||||
@@ -814,9 +852,7 @@ describe("LLM Wiki plugin scaffold", () => {
|
||||
|
||||
it("routes legacy Wiki operations URLs to the History issue table", () => {
|
||||
mockPathname = "/PAP/wiki/operations";
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("Issues table · project-1 · plugin:paperclipai.plugin-llm-wiki:operation");
|
||||
expect(markup).not.toContain("Recent runs");
|
||||
@@ -825,9 +861,7 @@ describe("LLM Wiki plugin scaffold", () => {
|
||||
|
||||
it("loads AGENTS.md from the Wiki page and exposes an edit affordance", () => {
|
||||
mockPathname = "/PAP/wiki/page/templates/AGENTS.md";
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("AGENTS");
|
||||
expect(markup).toContain(">AGENTS.md</h1>");
|
||||
@@ -851,9 +885,7 @@ describe("LLM Wiki plugin scaffold", () => {
|
||||
sourceRefs: [{ kind: "issue", issueIdentifier: "PAP-FOOTER-1" }],
|
||||
};
|
||||
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("Current project state.");
|
||||
expect(markup).not.toContain(">Sources</");
|
||||
@@ -878,9 +910,7 @@ updated: 2026-05-04
|
||||
Route sidebar state stays attached to the selected wiki page.
|
||||
`;
|
||||
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("<summary");
|
||||
expect(markup).toContain("Properties");
|
||||
@@ -918,9 +948,7 @@ Wiki links preserve normal browser navigation.
|
||||
Duplicate headings receive stable suffixes.
|
||||
`;
|
||||
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain('aria-label="On this page"');
|
||||
expect(markup).toContain('aria-expanded="true"');
|
||||
@@ -959,9 +987,7 @@ Duplicate headings receive stable suffixes.
|
||||
checkedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("Repair wiki root folder");
|
||||
expect(markup).toContain("/tmp/deleted-wiki-root");
|
||||
@@ -997,9 +1023,7 @@ Duplicate headings receive stable suffixes.
|
||||
|
||||
it("renders the maintainer settings without plugin metadata or inline AGENTS.md editing", () => {
|
||||
mockPathname = "/PAP/wiki/settings/maintainer";
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("Wiki Maintainer");
|
||||
expect(markup).toContain("Adapter: claude local");
|
||||
@@ -1024,9 +1048,7 @@ Duplicate headings receive stable suffixes.
|
||||
details: { name: "Wiki Maintainer", status: "pending_approval", adapterType: "claude_local", icon: "book-open", urlKey: "wiki-maintainer" },
|
||||
};
|
||||
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("pending approval");
|
||||
expect(markup).toContain("Approve the agent");
|
||||
@@ -1035,9 +1057,7 @@ Duplicate headings receive stable suffixes.
|
||||
|
||||
it("renders root settings as a compact health checklist with the shared path picker", () => {
|
||||
mockPathname = "/PAP/wiki/settings";
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain(">Setup</");
|
||||
expect(markup).toContain("Base Folder");
|
||||
@@ -1107,9 +1127,7 @@ Duplicate headings receive stable suffixes.
|
||||
checkedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("configuration errors detected, fix them all?");
|
||||
expect(markup).toContain("Wiki root folder");
|
||||
@@ -1132,9 +1150,7 @@ Duplicate headings receive stable suffixes.
|
||||
},
|
||||
}];
|
||||
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("Managed Skills");
|
||||
expect(markup).toContain("skill issue(s) need attention");
|
||||
@@ -1156,9 +1172,7 @@ Duplicate headings receive stable suffixes.
|
||||
},
|
||||
}];
|
||||
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("skill issue(s) need attention");
|
||||
expect(markup).toContain("LLM Wiki Maintainer differs from the plugin default: SKILL.md.");
|
||||
@@ -1178,9 +1192,7 @@ Duplicate headings receive stable suffixes.
|
||||
|
||||
it("renders project settings as a project picker without managed-resource metadata", () => {
|
||||
mockPathname = "/PAP/wiki/settings/project";
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("Use existing project");
|
||||
expect(markup).toContain("LLM Wiki");
|
||||
@@ -1199,9 +1211,7 @@ Duplicate headings receive stable suffixes.
|
||||
it("renders space settings with health line styling and disabled access controls", () => {
|
||||
mockPathname = "/PAP/wiki/settings/spaces/default";
|
||||
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("Space folder health checklist");
|
||||
expect(markup).toContain("left:8px");
|
||||
@@ -1231,9 +1241,7 @@ Duplicate headings receive stable suffixes.
|
||||
runs: [],
|
||||
};
|
||||
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("Agent execution");
|
||||
expect(markup).toContain("Assigned maintainer");
|
||||
@@ -1248,7 +1256,7 @@ Duplicate headings receive stable suffixes.
|
||||
expect(markup).not.toContain("Monthly plugin cap");
|
||||
});
|
||||
|
||||
it("renders managed routines as normal routine rows with run, toggle, and configure controls", () => {
|
||||
it("passes managed routine titles, bindings, and configuration links to the host list", () => {
|
||||
mockPathname = "/PAP/wiki/settings/routines";
|
||||
mockSettingsManagedRoutines = [{
|
||||
status: "resolved",
|
||||
@@ -1265,14 +1273,10 @@ Duplicate headings receive stable suffixes.
|
||||
},
|
||||
details: { cronExpression: "0 3 * * *" },
|
||||
}];
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("Run LLM Wiki lint");
|
||||
expect(markup).toContain("Run now");
|
||||
expect(markup).toContain("Configure");
|
||||
expect(markup).toContain("role=\"switch\"");
|
||||
expect(markup).toContain("LLM Wiki · Wiki Maintainer");
|
||||
expect(markup).toContain("href=\"/PAP/routines/routine-1\"");
|
||||
});
|
||||
@@ -1299,9 +1303,7 @@ Duplicate headings receive stable suffixes.
|
||||
details: { cronExpression: "0 3 * * *" },
|
||||
}];
|
||||
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("Routine defaults changed");
|
||||
expect(markup).toContain("Plugin defaults changed: description");
|
||||
@@ -1319,9 +1321,7 @@ Duplicate headings receive stable suffixes.
|
||||
details: { name: "Wiki Maintainer", status: "idle", adapterType: "claude_local", icon: "book-open", urlKey: "wiki-maintainer" },
|
||||
};
|
||||
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("Wiki Maintainer instructions differ from the plugin default: AGENTS.md.");
|
||||
expect(markup).toContain("Wiki Maintainer instruction defaults changed: AGENTS.md");
|
||||
@@ -1344,9 +1344,7 @@ Duplicate headings receive stable suffixes.
|
||||
details: { cronExpression: "0 3 * * *" },
|
||||
}];
|
||||
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("Routine setup needs repair");
|
||||
expect(markup).toContain("Fix routines");
|
||||
@@ -1358,9 +1356,7 @@ Duplicate headings receive stable suffixes.
|
||||
|
||||
it("renders legacy lint routes inside the Wiki settings section", () => {
|
||||
mockPathname = "/PAP/wiki/lint";
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("Run lint now");
|
||||
expect(markup).toContain("Recent lint runs");
|
||||
@@ -1369,9 +1365,7 @@ Duplicate headings receive stable suffixes.
|
||||
|
||||
it("does not expose IDEA.md pattern editing as a settings section", () => {
|
||||
mockPathname = "/PAP/wiki/settings";
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("LLM Wiki settings sections");
|
||||
expect(markup).not.toContain(">Pattern</span>");
|
||||
@@ -1381,9 +1375,7 @@ Duplicate headings receive stable suffixes.
|
||||
|
||||
it("does not expose plugin capabilities as a settings section", () => {
|
||||
mockPathname = "/PAP/wiki/settings";
|
||||
const markup = renderToStaticMarkup(createElement(WikiPage, {
|
||||
context: { companyId: COMPANY_ID, companyPrefix: "PAP" },
|
||||
} as never));
|
||||
const markup = renderWikiPage();
|
||||
|
||||
expect(markup).toContain("LLM Wiki settings sections");
|
||||
expect(markup).not.toContain("Plugin capabilities");
|
||||
@@ -1392,11 +1384,7 @@ Duplicate headings receive stable suffixes.
|
||||
|
||||
it("reconciles managed maintenance routines through stable agent and project refs", async () => {
|
||||
const harness = createTestHarness({ manifest });
|
||||
const writes: Array<{ path: string; contents: string }> = [];
|
||||
harness.ctx.localFolders.writeTextAtomic = async (_companyId, _folderKey, relativePath, contents) => {
|
||||
writes.push({ path: relativePath, contents });
|
||||
return harness.ctx.localFolders.status(COMPANY_ID, "wiki-root");
|
||||
};
|
||||
const writes = captureWikiWrites(harness);
|
||||
|
||||
await plugin.definition.setup(harness.ctx);
|
||||
|
||||
@@ -1651,11 +1639,7 @@ Duplicate headings receive stable suffixes.
|
||||
const harness = createTestHarness({ manifest });
|
||||
const issue = paperclipIssue();
|
||||
harness.seed({ issues: [issue] });
|
||||
const writes: Array<{ path: string; contents: string }> = [];
|
||||
harness.ctx.localFolders.writeTextAtomic = async (_companyId, _folderKey, relativePath, contents) => {
|
||||
writes.push({ path: relativePath, contents });
|
||||
return harness.ctx.localFolders.status(COMPANY_ID, "wiki-root");
|
||||
};
|
||||
const writes = captureWikiWrites(harness);
|
||||
|
||||
await plugin.definition.setup(harness.ctx);
|
||||
await harness.emit("issue.created", { identifier: issue.identifier }, {
|
||||
@@ -1674,11 +1658,7 @@ Duplicate headings receive stable suffixes.
|
||||
const harness = createTestHarness({ manifest });
|
||||
const issue = paperclipIssue();
|
||||
harness.seed({ issues: [issue] });
|
||||
const writes: Array<{ path: string; contents: string }> = [];
|
||||
harness.ctx.localFolders.writeTextAtomic = async (_companyId, _folderKey, relativePath, contents) => {
|
||||
writes.push({ path: relativePath, contents });
|
||||
return harness.ctx.localFolders.status(COMPANY_ID, "wiki-root");
|
||||
};
|
||||
const writes = captureWikiWrites(harness);
|
||||
|
||||
await plugin.definition.setup(harness.ctx);
|
||||
const policy = await harness.performAction<{ enabled: boolean; sources: { issues: boolean; comments: boolean; documents: boolean } }>(
|
||||
@@ -2768,11 +2748,7 @@ Duplicate headings receive stable suffixes.
|
||||
|
||||
it("bootstraps required local wiki files through the local folder API", async () => {
|
||||
const harness = createTestHarness({ manifest });
|
||||
const writes: Array<{ path: string; contents: string }> = [];
|
||||
harness.ctx.localFolders.writeTextAtomic = async (_companyId, _folderKey, relativePath, contents) => {
|
||||
writes.push({ path: relativePath, contents });
|
||||
return harness.ctx.localFolders.status(COMPANY_ID, "wiki-root");
|
||||
};
|
||||
const writes = captureWikiWrites(harness);
|
||||
|
||||
await plugin.definition.setup(harness.ctx);
|
||||
const result = await harness.performAction<{ writtenFiles: string[]; managedSkills: WikiSkillResource[] }>("bootstrap-root", {
|
||||
@@ -3009,11 +2985,7 @@ Duplicate headings receive stable suffixes.
|
||||
|
||||
it("captures raw sources into local files and plugin metadata", async () => {
|
||||
const harness = createTestHarness({ manifest });
|
||||
const writes: Array<{ path: string; contents: string }> = [];
|
||||
harness.ctx.localFolders.writeTextAtomic = async (_companyId, _folderKey, relativePath, contents) => {
|
||||
writes.push({ path: relativePath, contents });
|
||||
return harness.ctx.localFolders.status(COMPANY_ID, "wiki-root");
|
||||
};
|
||||
const writes = captureWikiWrites(harness);
|
||||
|
||||
await plugin.definition.setup(harness.ctx);
|
||||
const result = await harness.performAction<{ rawPath: string; hash: string }>("capture-source", {
|
||||
@@ -3031,11 +3003,7 @@ Duplicate headings receive stable suffixes.
|
||||
|
||||
it("preserves manual source ingest for non-default spaces while refusing Paperclip distillation there", async () => {
|
||||
const harness = createTestHarness({ manifest });
|
||||
const writes: Array<{ path: string; contents: string }> = [];
|
||||
harness.ctx.localFolders.writeTextAtomic = async (_companyId, _folderKey, relativePath, contents) => {
|
||||
writes.push({ path: relativePath, contents });
|
||||
return harness.ctx.localFolders.status(COMPANY_ID, "wiki-root");
|
||||
};
|
||||
const writes = captureWikiWrites(harness);
|
||||
harness.seed({
|
||||
agents: [wikiMaintainerAgent()],
|
||||
projects: [existingProject()],
|
||||
@@ -3057,26 +3025,7 @@ Duplicate headings receive stable suffixes.
|
||||
companyId: COMPANY_ID,
|
||||
displayName: "Team Research",
|
||||
});
|
||||
spaces.set(created.space.slug, {
|
||||
id: created.space.id,
|
||||
company_id: created.space.companyId,
|
||||
wiki_id: created.space.wikiId,
|
||||
slug: created.space.slug,
|
||||
display_name: created.space.displayName,
|
||||
space_type: "local_folder",
|
||||
folder_mode: "managed_subfolder",
|
||||
root_folder_key: "wiki-root",
|
||||
path_prefix: created.space.pathPrefix,
|
||||
configured_root_path: null,
|
||||
access_scope: "shared",
|
||||
owner_user_id: null,
|
||||
owner_agent_id: null,
|
||||
team_key: null,
|
||||
settings: {},
|
||||
status: "active",
|
||||
created_at: null,
|
||||
updated_at: null,
|
||||
});
|
||||
spaces.set(created.space.slug, managedSpaceRow(created));
|
||||
|
||||
const source = await harness.performAction<{ rawPath: string; spaceSlug: string }>("capture-source", {
|
||||
companyId: COMPANY_ID,
|
||||
@@ -3299,26 +3248,7 @@ Duplicate headings receive stable suffixes.
|
||||
companyId: COMPANY_ID,
|
||||
displayName: "Research Space",
|
||||
});
|
||||
spaces.set(created.space.slug, {
|
||||
id: created.space.id,
|
||||
company_id: created.space.companyId,
|
||||
wiki_id: created.space.wikiId,
|
||||
slug: created.space.slug,
|
||||
display_name: created.space.displayName,
|
||||
space_type: "local_folder",
|
||||
folder_mode: "managed_subfolder",
|
||||
root_folder_key: "wiki-root",
|
||||
path_prefix: created.space.pathPrefix,
|
||||
configured_root_path: null,
|
||||
access_scope: "shared",
|
||||
owner_user_id: null,
|
||||
owner_agent_id: null,
|
||||
team_key: null,
|
||||
settings: {},
|
||||
status: "active",
|
||||
created_at: null,
|
||||
updated_at: null,
|
||||
});
|
||||
spaces.set(created.space.slug, managedSpaceRow(created));
|
||||
await harness.performAction("bootstrap-space", {
|
||||
companyId: COMPANY_ID,
|
||||
spaceSlug: created.space.slug,
|
||||
@@ -3364,6 +3294,8 @@ Duplicate headings receive stable suffixes.
|
||||
|
||||
const pageWrites = harness.dbExecutes.filter((execute) => execute.sql.includes("wiki_pages"));
|
||||
const sourceWrites = harness.dbExecutes.filter((execute) => execute.sql.includes("wiki_sources"));
|
||||
expect(pageWrites).toHaveLength(4);
|
||||
expect(sourceWrites).toHaveLength(2);
|
||||
expect(pageWrites.every((write) => write.sql.includes("space_id"))).toBe(true);
|
||||
expect(sourceWrites.every((write) => write.sql.includes("space_id"))).toBe(true);
|
||||
});
|
||||
@@ -3371,11 +3303,7 @@ Duplicate headings receive stable suffixes.
|
||||
it("ingests source metadata and creates a hidden plugin-operation issue", async () => {
|
||||
const harness = createTestHarness({ manifest });
|
||||
harness.seed({ agents: [wikiMaintainerAgent()] });
|
||||
const writes: Array<{ path: string; contents: string }> = [];
|
||||
harness.ctx.localFolders.writeTextAtomic = async (_companyId, _folderKey, relativePath, contents) => {
|
||||
writes.push({ path: relativePath, contents });
|
||||
return harness.ctx.localFolders.status(COMPANY_ID, "wiki-root");
|
||||
};
|
||||
const writes = captureWikiWrites(harness);
|
||||
|
||||
await plugin.definition.setup(harness.ctx);
|
||||
const result = await harness.performAction<{
|
||||
@@ -3429,11 +3357,7 @@ Duplicate headings receive stable suffixes.
|
||||
it("rejects oversized source capture before raw writes or operation creation", async () => {
|
||||
const harness = createTestHarness({ manifest, config: { maxSourceBytes: 16 } });
|
||||
harness.seed({ agents: [wikiMaintainerAgent()] });
|
||||
const writes: Array<{ path: string; contents: string }> = [];
|
||||
harness.ctx.localFolders.writeTextAtomic = async (_companyId, _folderKey, relativePath, contents) => {
|
||||
writes.push({ path: relativePath, contents });
|
||||
return harness.ctx.localFolders.status(COMPANY_ID, "wiki-root");
|
||||
};
|
||||
const writes = captureWikiWrites(harness);
|
||||
|
||||
await plugin.definition.setup(harness.ctx);
|
||||
await expect(harness.performAction("ingest-source", {
|
||||
@@ -3568,26 +3492,7 @@ Duplicate headings receive stable suffixes.
|
||||
companyId: COMPANY_ID,
|
||||
displayName: "Research Space",
|
||||
});
|
||||
spaces.set(created.space.slug, {
|
||||
id: created.space.id,
|
||||
company_id: created.space.companyId,
|
||||
wiki_id: created.space.wikiId,
|
||||
slug: created.space.slug,
|
||||
display_name: created.space.displayName,
|
||||
space_type: "local_folder",
|
||||
folder_mode: "managed_subfolder",
|
||||
root_folder_key: "wiki-root",
|
||||
path_prefix: created.space.pathPrefix,
|
||||
configured_root_path: null,
|
||||
access_scope: "shared",
|
||||
owner_user_id: null,
|
||||
owner_agent_id: null,
|
||||
team_key: null,
|
||||
settings: {},
|
||||
status: "active",
|
||||
created_at: null,
|
||||
updated_at: null,
|
||||
});
|
||||
spaces.set(created.space.slug, managedSpaceRow(created));
|
||||
|
||||
const result = await harness.performAction<{
|
||||
operationId: string;
|
||||
@@ -3669,6 +3574,13 @@ Duplicate headings receive stable suffixes.
|
||||
it("starts query sessions, records run ids, and forwards session events to plugin streams", async () => {
|
||||
const harness = createTestHarness({ manifest });
|
||||
harness.seed({ agents: [wikiMaintainerAgent()] });
|
||||
let issuedRunId: string | undefined;
|
||||
const originalSendMessage = harness.ctx.agents.sessions.sendMessage.bind(harness.ctx.agents.sessions);
|
||||
harness.ctx.agents.sessions.sendMessage = async (...args) => {
|
||||
const receipt = await originalSendMessage(...args);
|
||||
issuedRunId = receipt.runId;
|
||||
return receipt;
|
||||
};
|
||||
const streamEvents: unknown[] = [];
|
||||
harness.ctx.streams.emit = (_channel, event) => {
|
||||
streamEvents.push(event);
|
||||
@@ -3689,7 +3601,15 @@ Duplicate headings receive stable suffixes.
|
||||
expect(result.querySessionId).toBe(result.operationId);
|
||||
expect(result.channel).toBe(`llm-wiki:query:${result.operationId}`);
|
||||
expect(harness.dbExecutes.some((execute) => execute.sql.includes("wiki_query_sessions"))).toBe(true);
|
||||
expect(harness.dbExecutes.some((execute) => execute.sql.includes("run_ids"))).toBe(true);
|
||||
expect(issuedRunId).toEqual(expect.any(String));
|
||||
expect(issuedRunId).not.toBe("");
|
||||
expect(result.runId).toBe(issuedRunId);
|
||||
const runUpdate = harness.dbExecutes.find((execute) =>
|
||||
/UPDATE .*wiki_operations/.test(execute.sql) && execute.sql.includes("run_ids"),
|
||||
);
|
||||
expect(runUpdate?.params?.slice(0, 4)).toEqual([
|
||||
COMPANY_ID, result.operationId, "running", JSON.stringify([issuedRunId]),
|
||||
]);
|
||||
|
||||
harness.simulateSessionEvent(result.sessionId, {
|
||||
runId: result.runId,
|
||||
@@ -3722,11 +3642,7 @@ Duplicate headings receive stable suffixes.
|
||||
it("files a streamed query answer as a page through a hidden file-as-page operation", async () => {
|
||||
const harness = createTestHarness({ manifest });
|
||||
harness.seed({ agents: [wikiMaintainerAgent()] });
|
||||
const writes: Array<{ path: string; contents: string }> = [];
|
||||
harness.ctx.localFolders.writeTextAtomic = async (_companyId, _folderKey, relativePath, contents) => {
|
||||
writes.push({ path: relativePath, contents });
|
||||
return harness.ctx.localFolders.status(COMPANY_ID, "wiki-root");
|
||||
};
|
||||
const writes = captureWikiWrites(harness);
|
||||
await plugin.definition.setup(harness.ctx);
|
||||
|
||||
const result = await harness.performAction<{ path: string; operationId: string; page: { revisionId: string } }>(
|
||||
|
||||
@@ -763,11 +763,6 @@ describe("AppDefinition catalog", () => {
|
||||
APP_DEFINITIONS.find((app) => app.slug === "linear")?.methods[0]?.defaults
|
||||
?.scopesHint,
|
||||
).toEqual(["read", "write"]));
|
||||
it("requests Hugging Face MCP read, contribution and job scopes", () =>
|
||||
expect(
|
||||
APP_DEFINITIONS.find((app) => app.slug === "hugging-face")?.methods[0]
|
||||
?.defaults?.scopesHint,
|
||||
).toEqual(["read-mcp", "read-repos", "contribute-repos", "jobs"]));
|
||||
it("defaults every new connection action to allowed except Enterpret writes", () => {
|
||||
for (const app of APP_DEFINITIONS)
|
||||
for (const method of app.methods) {
|
||||
@@ -1327,21 +1322,6 @@ describe("AppDefinition catalog", () => {
|
||||
?.available,
|
||||
).toBe(false);
|
||||
});
|
||||
it("enforces method and field invariants", () => {
|
||||
for (const app of APP_DEFINITIONS)
|
||||
for (const method of app.methods) {
|
||||
if (
|
||||
method.auth === "api_key" &&
|
||||
(method.purpose ?? "tool") !== "channel"
|
||||
)
|
||||
expect(method.keyPlacement).toBeTruthy();
|
||||
if (method.auth === "oauth")
|
||||
expect(method.ownershipModes.length).toBeGreaterThan(0);
|
||||
for (const field of method.credentialFields ?? [])
|
||||
if (field.required && field.type !== "checkbox")
|
||||
expect(field.placeholder).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export function buildSlackAppManifest(input: {
|
||||
redirectUri?: string;
|
||||
}) {
|
||||
return {
|
||||
display_information: { name: input.app.appName },
|
||||
display_information: { name: input.app.appName, description: `Talk to ${input.agentName} and manage work in Paperclip.`.slice(0, 140) },
|
||||
features: {
|
||||
app_home: { home_tab_enabled: false, messages_tab_enabled: true, messages_tab_read_only_enabled: false },
|
||||
agent_view: { agent_description: "Work with a Paperclip agent in a task-backed conversation." },
|
||||
@@ -60,6 +60,7 @@ export const slackRegistrationStateSchema = z.object({
|
||||
status: slackRegistrationStatusSchema,
|
||||
appId: z.string().nullable().optional(),
|
||||
managementUrl: z.string().url().nullable().optional(),
|
||||
managerGrantId: z.string().uuid().nullable().optional(),
|
||||
errorCode: z.string().nullable().optional(),
|
||||
}).strict();
|
||||
export type SlackRegistrationState = z.infer<typeof slackRegistrationStateSchema>;
|
||||
@@ -79,11 +80,33 @@ export const slackAccountStateSchema = z.object({
|
||||
}).strict();
|
||||
export type SlackAccountState = z.infer<typeof slackAccountStateSchema>;
|
||||
export const slackInstallAuthorizationSchema = z.object({ authorizationUrl: z.string().url(), expiresAt: z.string().datetime() });
|
||||
export const slackManagerAuthorizationSchema = slackInstallAuthorizationSchema.extend({ handoff: z.object({ kind: z.literal("paperclip_cloud"), session: z.string().min(16).max(512) }).optional() });
|
||||
export type SlackManagerAuthorization = z.infer<typeof slackManagerAuthorizationSchema>;
|
||||
export type SlackInstallAuthorization = z.infer<typeof slackInstallAuthorizationSchema>;
|
||||
export const slackSetupActionSchema = z.object({}).strict();
|
||||
|
||||
export const slackTestMessageSchema = z.object({
|
||||
requestId: z.string().uuid(),
|
||||
slackUserId: z.string().regex(/^[UW][A-Z0-9]+$/).optional(),
|
||||
accountCursor: z.string().min(1).max(512).optional(),
|
||||
}).strict();
|
||||
export type SlackTestMessageInput = z.infer<typeof slackTestMessageSchema>;
|
||||
export const slackTestMessageResultSchema = z.discriminatedUnion("status", [
|
||||
z.object({ status: z.literal("sent"), slackUrl: z.string().url() }).strict(),
|
||||
z.object({ status: z.literal("connection_required"), accounts: z.array(z.object({
|
||||
id: z.string(), name: z.string(),
|
||||
}).strict()), nextCursor: z.string().nullable() }).strict(),
|
||||
z.object({ status: z.literal("connection_pending"), principalId: z.string().uuid(), expiresAt: z.string().datetime() }).strict(),
|
||||
]);
|
||||
export type SlackTestMessageResult = z.infer<typeof slackTestMessageResultSchema>;
|
||||
|
||||
export function slackRegistrationErrorMessage(code: string): string {
|
||||
const messages: Record<string, string> = {
|
||||
slack_managed_unavailable: "Managed Slack setup is unavailable. Try again later or use your own app before creation starts.",
|
||||
slack_manager_reauthorize: "Reconnect your Slack workspace to continue setting up this agent.",
|
||||
slack_approval_pending: "Slack is waiting for your workspace administrator to approve this app.",
|
||||
slack_approval_required: "Your Slack workspace requires approval. Continue in Slack to request it.",
|
||||
slack_approval_denied: "Your Slack administrator declined this installation. Contact them to continue.",
|
||||
slack_configuration_token_invalid: "Slack rejected the app configuration access token. Generate a new access token and try again.",
|
||||
slack_manifest_invalid: "Slack rejected this app configuration. Check the app details or use manual setup.",
|
||||
slack_manifest_update_pending: "Your Slack app is saved, but its event settings still need to be applied. Enter an app configuration access token to retry configuring the same app.",
|
||||
@@ -96,9 +119,29 @@ export function slackRegistrationErrorMessage(code: string): string {
|
||||
slack_install_identity_mismatch: "Slack returned a different app, workspace, or bot. Install the app created for this connection.",
|
||||
slack_install_scopes_missing: "Slack did not grant all required bot permissions. Install the app again and approve its requested permissions.",
|
||||
slack_bot_already_connected: "This Slack bot is already connected to Paperclip. Resume its existing connection or use a different app.",
|
||||
slack_install_token_invalid: "Slack rejected the saved bot credentials. Continue in Slack to authorize this app again.",
|
||||
slack_configuration_incomplete: "The app is installed, but Paperclip could not finish connecting it. Retry connecting the saved installation.",
|
||||
slack_install_account_missing: "Slack did not identify your account. Authorize the same app again to connect your Slack account.",
|
||||
slack_install_account_conflict: "This Slack account already has an existing link. Manage account links in connection settings, or authorize with your own Slack account.",
|
||||
};
|
||||
return messages[code] ?? "Slack setup could not be completed. Resume setup and try again.";
|
||||
}
|
||||
|
||||
/** Persisted legacy values keep their original meaning. Missing means manual. */
|
||||
export type SlackSetupMethod = "managed" | "automatic" | "manual" | "existing";
|
||||
export function isProvisionedSlackSetup(method: string | undefined): boolean {
|
||||
return method === "automatic" || method === "managed";
|
||||
}
|
||||
export const SLACK_MANAGER_SCOPES = ["app_configurations:read", "app_configurations:write", "managed_apps:install"] as const;
|
||||
export const slackSetupOptionsSchema = z.object({
|
||||
managedAvailable: z.boolean(),
|
||||
defaultMethod: z.enum(["managed", "automatic"]),
|
||||
workspaces: z.array(z.object({ grantId: z.string().uuid(), workspaceId: z.string(), workspaceName: z.string(), userId: z.string() })),
|
||||
});
|
||||
export type SlackSetupOptions = z.infer<typeof slackSetupOptionsSchema>;
|
||||
export const slackManagedProvisionSchema = z.object({
|
||||
requestId: z.string().uuid(), grantId: z.string().uuid(), confirmedNoAppCreated: z.boolean().optional(), reconnect: z.boolean().optional(),
|
||||
}).strict();
|
||||
export type SlackManagedProvisionInput = z.infer<typeof slackManagedProvisionSchema>;
|
||||
export const slackManagedResultSchema = z.object({ authorization: slackInstallAuthorizationSchema.optional() });
|
||||
export type SlackManagedResult = z.infer<typeof slackManagedResultSchema>;
|
||||
@@ -190,7 +190,8 @@ export interface SlackAppConfiguration {
|
||||
}
|
||||
|
||||
export interface ChatEndpointSetupState {
|
||||
slackSetupMethod?: "automatic" | "manual" | "existing";
|
||||
slackSetupMethod?: "managed" | "automatic" | "manual" | "existing";
|
||||
slackManagerError?: string;
|
||||
slackRegistration?: import("../slack-app-manifest.js").SlackRegistrationState;
|
||||
/** Provider-confirmed app icon outcome; configuration tokens are never retained. */
|
||||
slackAvatar?: import("../slack-app-manifest.js").SlackAvatarState;
|
||||
@@ -508,10 +509,11 @@ export interface CreateChatEndpointInput {
|
||||
applicationId?: string;
|
||||
name?: string;
|
||||
slackApp?: SlackAppConfiguration;
|
||||
slackSetupMethod?: "managed" | "automatic" | "manual" | "existing";
|
||||
}
|
||||
|
||||
export interface UpdateChatEndpointInput {
|
||||
slackSetupMethod?: "automatic" | "manual" | "existing";
|
||||
slackSetupMethod?: "managed" | "automatic" | "manual" | "existing";
|
||||
slackApp?: SlackAppConfiguration;
|
||||
communicationInstructions?: string;
|
||||
allowDirectMessages?: boolean;
|
||||
|
||||
@@ -80,9 +80,10 @@ export const createChatEndpointSchema = z
|
||||
applicationId: z.string().uuid().optional(),
|
||||
name: z.string().trim().min(1).max(160).optional(),
|
||||
slackApp: slackAppConfigurationSchema.optional(),
|
||||
slackSetupMethod: z.enum(["managed", "automatic", "manual", "existing"]).optional(),
|
||||
})
|
||||
.strict()
|
||||
.refine(value => !value.slackApp || value.provider === "slack", {
|
||||
.refine(value => (!value.slackApp && !value.slackSetupMethod) || value.provider === "slack", {
|
||||
message: "Slack app details only apply to Slack connections", path: ["slackApp"],
|
||||
});
|
||||
|
||||
@@ -90,7 +91,7 @@ export const updateChatEndpointSchema = z
|
||||
.object({
|
||||
communicationInstructions: multilineTextSchema.pipe(z.string().trim().max(4000)).optional(),
|
||||
slackApp: slackAppConfigurationSchema.optional(),
|
||||
slackSetupMethod: z.enum(["automatic", "manual", "existing"]).optional(),
|
||||
slackSetupMethod: z.enum(["managed", "automatic", "manual", "existing"]).optional(),
|
||||
allowDirectMessages: z.boolean().optional(),
|
||||
requireAtMention: z.boolean().optional(),
|
||||
allowGroupChats: z.boolean().optional(),
|
||||
|
||||
@@ -7,7 +7,7 @@ import { resolveCanonicalWorktreeSeedSource } from "./worktree-seed-source.js";
|
||||
const cleanup: string[] = [];
|
||||
|
||||
function makeInstance(prefix: string, instanceId: string) {
|
||||
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
const cwd = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), prefix));
|
||||
cleanup.push(cwd);
|
||||
const configDir = path.join(cwd, ".paperclip");
|
||||
const configPath = path.join(configDir, "config.json");
|
||||
@@ -22,7 +22,7 @@ function makeInstance(prefix: string, instanceId: string) {
|
||||
* names its instance by directory and has no adjacent .env.
|
||||
*/
|
||||
function makeInstanceRoot(instanceId: string) {
|
||||
const home = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-seed-home-"));
|
||||
const home = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-seed-home-"));
|
||||
cleanup.push(home);
|
||||
const configDir = path.join(home, "instances", instanceId);
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
@@ -33,7 +33,7 @@ function makeInstanceRoot(instanceId: string) {
|
||||
|
||||
/** A managed project checkout: a plain clone with no `.paperclip` of its own. */
|
||||
function makePlainCheckout() {
|
||||
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-seed-checkout-"));
|
||||
const cwd = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-seed-checkout-"));
|
||||
cleanup.push(cwd);
|
||||
return cwd;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user