teamcopilot 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +10 -0
- package/LICENSE.md +21 -0
- package/README.md +131 -0
- package/bin/teamcopilot.js +281 -0
- package/dist/auth/index.js +189 -0
- package/dist/change-user-role.js +77 -0
- package/dist/chat/index.js +849 -0
- package/dist/constants.js +2 -0
- package/dist/create-user.js +98 -0
- package/dist/cronjob/index.js +16 -0
- package/dist/cronjob/resource-reconciliation.js +33 -0
- package/dist/delete-user.js +66 -0
- package/dist/frontend/assets/abap-CRCWOmpq.js +1 -0
- package/dist/frontend/assets/apex-DnsZk_dE.js +1 -0
- package/dist/frontend/assets/azcli-1IWB1ccx.js +1 -0
- package/dist/frontend/assets/bat-DPkNLes8.js +1 -0
- package/dist/frontend/assets/bicep-Corcdgou.js +2 -0
- package/dist/frontend/assets/cameligo-CGrWLZr3.js +1 -0
- package/dist/frontend/assets/clojure-D9WOWImG.js +1 -0
- package/dist/frontend/assets/codicon-DCmgc-ay.ttf +0 -0
- package/dist/frontend/assets/coffee-B7EJu28W.js +1 -0
- package/dist/frontend/assets/cpp-SEyurbux.js +1 -0
- package/dist/frontend/assets/csharp-BoL64M5l.js +1 -0
- package/dist/frontend/assets/csp-C46ZqvIl.js +1 -0
- package/dist/frontend/assets/css-DQU6DXDx.js +3 -0
- package/dist/frontend/assets/cssMode-BDT3WbVs.js +4 -0
- package/dist/frontend/assets/cypher-D84EuPTj.js +1 -0
- package/dist/frontend/assets/dart-D8lhlL1r.js +1 -0
- package/dist/frontend/assets/dockerfile-DLk6rpji.js +1 -0
- package/dist/frontend/assets/ecl-BO6FnfXk.js +1 -0
- package/dist/frontend/assets/editor.worker-B4pQIWZD.js +12 -0
- package/dist/frontend/assets/elixir-BRjLKONM.js +1 -0
- package/dist/frontend/assets/flow9-Cac8vKd7.js +1 -0
- package/dist/frontend/assets/freemarker2-C7-hEgID.js +3 -0
- package/dist/frontend/assets/fsharp-fd1GTHhf.js +1 -0
- package/dist/frontend/assets/go-O9LJTZXk.js +1 -0
- package/dist/frontend/assets/graphql-LQdxqEYJ.js +1 -0
- package/dist/frontend/assets/handlebars-4cwTkPir.js +1 -0
- package/dist/frontend/assets/hcl-DxDQ3s82.js +1 -0
- package/dist/frontend/assets/html-YNfE1Q0A.js +1 -0
- package/dist/frontend/assets/htmlMode-opTQ1HoB.js +4 -0
- package/dist/frontend/assets/index-DWyaVa1h.js +782 -0
- package/dist/frontend/assets/index-lXrsgeTF.css +1 -0
- package/dist/frontend/assets/ini-BvajGCUy.js +1 -0
- package/dist/frontend/assets/java-SYsfObOQ.js +1 -0
- package/dist/frontend/assets/javascript-BEwGzk7T.js +1 -0
- package/dist/frontend/assets/jsonMode-CGhIS5Al.js +10 -0
- package/dist/frontend/assets/julia-DQXNmw_w.js +1 -0
- package/dist/frontend/assets/kotlin-qQ0MG-9I.js +1 -0
- package/dist/frontend/assets/less-GGFNNJHn.js +2 -0
- package/dist/frontend/assets/lexon-Canl7DCW.js +1 -0
- package/dist/frontend/assets/liquid-QekTGCGJ.js +1 -0
- package/dist/frontend/assets/lua-D28Ae8-K.js +1 -0
- package/dist/frontend/assets/m3-DPitgjJI.js +1 -0
- package/dist/frontend/assets/markdown-B811l8j2.js +1 -0
- package/dist/frontend/assets/mdx-BAVDaB7v.js +1 -0
- package/dist/frontend/assets/mips-CdjsipkG.js +1 -0
- package/dist/frontend/assets/msdax-CYqgjx_P.js +1 -0
- package/dist/frontend/assets/mysql-BHd6q0vd.js +1 -0
- package/dist/frontend/assets/objective-c-B1aVtJYH.js +1 -0
- package/dist/frontend/assets/pascal-BhNW15KB.js +1 -0
- package/dist/frontend/assets/pascaligo-5jv8CcQD.js +1 -0
- package/dist/frontend/assets/perl-DlYyT36c.js +1 -0
- package/dist/frontend/assets/pgsql-Dy0bjov7.js +1 -0
- package/dist/frontend/assets/php-120yhfDK.js +1 -0
- package/dist/frontend/assets/pla-CjnFlu4u.js +1 -0
- package/dist/frontend/assets/postiats-CQpG440k.js +1 -0
- package/dist/frontend/assets/powerquery-DdJtto1Z.js +1 -0
- package/dist/frontend/assets/powershell-Bu_VLpJB.js +1 -0
- package/dist/frontend/assets/protobuf-IBS6jZEB.js +2 -0
- package/dist/frontend/assets/pug-kFxLfcjb.js +1 -0
- package/dist/frontend/assets/python-BQlHw7XO.js +1 -0
- package/dist/frontend/assets/qsharp-q7JyzKFN.js +1 -0
- package/dist/frontend/assets/r-BIFz-_sK.js +1 -0
- package/dist/frontend/assets/razor-Be3Wwc2E.js +1 -0
- package/dist/frontend/assets/redis-CHOsPHWR.js +1 -0
- package/dist/frontend/assets/redshift-CBifECDb.js +1 -0
- package/dist/frontend/assets/restructuredtext-CghPJEOS.js +1 -0
- package/dist/frontend/assets/ruby-CYWGW-b1.js +1 -0
- package/dist/frontend/assets/rust-DMDD0SHb.js +1 -0
- package/dist/frontend/assets/sb-BYAiYHFx.js +1 -0
- package/dist/frontend/assets/scala-Bqvq8jcR.js +1 -0
- package/dist/frontend/assets/scheme-Dhb-2j9p.js +1 -0
- package/dist/frontend/assets/scss-CTwUZ5N7.js +3 -0
- package/dist/frontend/assets/shell-CsDZo4DB.js +1 -0
- package/dist/frontend/assets/solidity-CME5AdoB.js +1 -0
- package/dist/frontend/assets/sophia-RYC1BQQz.js +1 -0
- package/dist/frontend/assets/sparql-KEyrF7De.js +1 -0
- package/dist/frontend/assets/sql-BdTr02Mf.js +1 -0
- package/dist/frontend/assets/st-C7iG7M4S.js +1 -0
- package/dist/frontend/assets/swift-D7IUmUK8.js +1 -0
- package/dist/frontend/assets/systemverilog-DgMryOEJ.js +1 -0
- package/dist/frontend/assets/tcl-PloMZuKG.js +1 -0
- package/dist/frontend/assets/tsMode-CIBFoN3z.js +11 -0
- package/dist/frontend/assets/twig-BfRIq3la.js +1 -0
- package/dist/frontend/assets/typescript-BuV9wEIE.js +1 -0
- package/dist/frontend/assets/typespec-CzxlYoT_.js +1 -0
- package/dist/frontend/assets/vb-BwAE3J76.js +1 -0
- package/dist/frontend/assets/wgsl-B_1kOXbF.js +298 -0
- package/dist/frontend/assets/xml-DcDKYaM4.js +1 -0
- package/dist/frontend/assets/yaml-CuBNmOuI.js +1 -0
- package/dist/frontend/index.html +14 -0
- package/dist/frontend/logo.svg +50 -0
- package/dist/index.js +169 -0
- package/dist/logging.js +30 -0
- package/dist/opencode-auth/index.js +122 -0
- package/dist/opencode-server.js +91 -0
- package/dist/prisma/client.js +38 -0
- package/dist/reset-password.js +73 -0
- package/dist/rotate-jwt-secret.js +20 -0
- package/dist/scripts/prisma-workspace.js +34 -0
- package/dist/skills/index.js +311 -0
- package/dist/types/permissions.js +2 -0
- package/dist/types/shared/permissions.js +17 -0
- package/dist/types/shared/skill.js +17 -0
- package/dist/types/shared/workflow-files.js +17 -0
- package/dist/types/shared/workflow.js +17 -0
- package/dist/types/skill.js +2 -0
- package/dist/types/workflow-files.js +2 -0
- package/dist/types/workflow.js +2 -0
- package/dist/users/index.js +22 -0
- package/dist/utils/approval-snapshot-common.js +596 -0
- package/dist/utils/assert.js +20 -0
- package/dist/utils/chat-session.js +44 -0
- package/dist/utils/cli-bootstrap.js +26 -0
- package/dist/utils/index.js +95 -0
- package/dist/utils/jwt-secret.js +63 -0
- package/dist/utils/opencode-auth.js +126 -0
- package/dist/utils/opencode-client.js +109 -0
- package/dist/utils/password-policy.js +12 -0
- package/dist/utils/permission-common.js +280 -0
- package/dist/utils/redact.js +108 -0
- package/dist/utils/resource-access.js +37 -0
- package/dist/utils/resource-file-routes.js +115 -0
- package/dist/utils/resource-files.js +572 -0
- package/dist/utils/runtime-paths.js +61 -0
- package/dist/utils/session-abort.js +52 -0
- package/dist/utils/skill-approval-snapshot.js +39 -0
- package/dist/utils/skill-files.js +17 -0
- package/dist/utils/skill-permissions.js +15 -0
- package/dist/utils/skill.js +217 -0
- package/dist/utils/user-role.js +14 -0
- package/dist/utils/workflow-approval-snapshot.js +38 -0
- package/dist/utils/workflow-files.js +17 -0
- package/dist/utils/workflow-interruption.js +50 -0
- package/dist/utils/workflow-permissions.js +27 -0
- package/dist/utils/workflow-runner.js +414 -0
- package/dist/utils/workflow.js +158 -0
- package/dist/utils/workspace-sync.js +204 -0
- package/dist/workflows/index.js +751 -0
- package/dist/workspace_files/.opencode/opencode.json +17 -0
- package/dist/workspace_files/.opencode/package.json +14 -0
- package/dist/workspace_files/.opencode/plugins/createSkill.ts +339 -0
- package/dist/workspace_files/.opencode/plugins/createWorkflow.ts +345 -0
- package/dist/workspace_files/.opencode/plugins/findSimilarWorkflow.ts +173 -0
- package/dist/workspace_files/.opencode/plugins/findSkill.ts +211 -0
- package/dist/workspace_files/.opencode/plugins/getSkillContent.ts +135 -0
- package/dist/workspace_files/.opencode/plugins/honeytoken-protection.ts +64 -0
- package/dist/workspace_files/.opencode/plugins/listAvailableSkills.ts +93 -0
- package/dist/workspace_files/.opencode/plugins/listAvailableWorkflows.ts +93 -0
- package/dist/workspace_files/.opencode/plugins/python-protection.ts +184 -0
- package/dist/workspace_files/.opencode/plugins/runWorkflow.ts +168 -0
- package/dist/workspace_files/.opencode/tsconfig.json +16 -0
- package/dist/workspace_files/AGENTS.md +483 -0
- package/dist/workspace_files/package-lock.json +167 -0
- package/dist/workspace_files/package.json +5 -0
- package/package.json +86 -0
- package/prisma/migrations/20260203040755_init/migration.sql +20 -0
- package/prisma/migrations/20260204034845_replace_google_auth_with_email_password/migration.sql +25 -0
- package/prisma/migrations/20260207022226_add_user_role/migration.sql +25 -0
- package/prisma/migrations/20260210161254_add_workflow_runs/migration.sql +16 -0
- package/prisma/migrations/20260211050606_adds_workflow_table/migration.sql +40 -0
- package/prisma/migrations/20260211050750_adds_fkey_constraint/migration.sql +21 -0
- package/prisma/migrations/20260211051912_removes_workflow_table/migration.sql +34 -0
- package/prisma/migrations/20260211052238_changes_workflow_id_to_slug/migration.sql +27 -0
- package/prisma/migrations/20260212051912_add_output_to_workflow_runs/migration.sql +2 -0
- package/prisma/migrations/20260213073006_add_chat_sessions/migration.sql +13 -0
- package/prisma/migrations/20260216053202_add_chat_sessions_opencode_session_id_idx/migration.sql +2 -0
- package/prisma/migrations/20260216053237_drop_redundant_chat_sessions_opencode_idx/migration.sql +2 -0
- package/prisma/migrations/20260219060705_makes/migration.sql +24 -0
- package/prisma/migrations/20260222040542_add_workflow_execution_permissions/migration.sql +18 -0
- package/prisma/migrations/20260222040815_remove_workflow_execution_permissions/migration.sql +10 -0
- package/prisma/migrations/20260222041348_add_workflow_execution_permissions_final/migration.sql +17 -0
- package/prisma/migrations/20260222041741_rename_to_tool_execution_permissions/migration.sql +30 -0
- package/prisma/migrations/20260222041826_simplify_tool_execution_permissions/migration.sql +29 -0
- package/prisma/migrations/20260222041950_add_fields_for_standalone_permissions/migration.sql +32 -0
- package/prisma/migrations/20260222042954_simplify_tool_permissions_table/migration.sql +27 -0
- package/prisma/migrations/20260223073902_add_workflow_run_permissions_tables/migration.sql +23 -0
- package/prisma/migrations/20260225025151_add_workflow_metadata/migration.sql +16 -0
- package/prisma/migrations/20260225031035_merge_workflow_permissions_into_metadata/migration.sql +44 -0
- package/prisma/migrations/20260225031752_removes_default_for_run_permission_mode/migration.sql +20 -0
- package/prisma/migrations/20260225033603_remove_workflow_metadata_user_fkeys/migration.sql +18 -0
- package/prisma/migrations/20260225043032_restore_workflow_metadata_user_fkeys/migration.sql +20 -0
- package/prisma/migrations/20260225091423_add_workflow_approved_snapshots/migration.sql +28 -0
- package/prisma/migrations/20260226032121_add_is_approved_to_workflow_metadata/migration.sql +21 -0
- package/prisma/migrations/20260226032444_undoes_last_db_change/migration.sql +26 -0
- package/prisma/migrations/20260227120000_remove_snapshot_hash_from_approved_snapshots/migration.sql +16 -0
- package/prisma/migrations/20260228071125_adds_workspace_path_to_snapshot_table/migration.sql +22 -0
- package/prisma/migrations/20260228071217_modifies_index_and_removes_default_value/migration.sql +22 -0
- package/prisma/migrations/20260228071710_undoes_previous/migration.sql +27 -0
- package/prisma/migrations/20260228105022_add_must_change_password_first_login/migration.sql +20 -0
- package/prisma/migrations/20260301115439_add_workflow_run_log_refs/migration.sql +8 -0
- package/prisma/migrations/20260301122557_add_workflow_aborted_sessions/migration.sql +5 -0
- package/prisma/migrations/20260302045545_move_workflow_run_log_refs_into_workflow_runs/migration.sql +17 -0
- package/prisma/migrations/20260303040318_add_skill_tables/migration.sql +61 -0
- package/prisma/migrations/20260303051533_unify_resource_permissions/migration.sql +97 -0
- package/prisma/migrations/20260303064255_unify_resource_metadata_and_snapshots/migration.sql +179 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +147 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.startWorkflowRunViaBackend = startWorkflowRunViaBackend;
|
|
40
|
+
// @ts-nocheck
|
|
41
|
+
const child_process_1 = require("child_process");
|
|
42
|
+
const fs_1 = require("fs");
|
|
43
|
+
const fsp = __importStar(require("fs/promises"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const client_1 = __importDefault(require("../prisma/client"));
|
|
46
|
+
const workflow_interruption_1 = require("./workflow-interruption");
|
|
47
|
+
const MAX_OUTPUT_CHARS = 300000;
|
|
48
|
+
const SLUG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
49
|
+
function sanitizeFilenamePart(value) {
|
|
50
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
51
|
+
}
|
|
52
|
+
function isPathInside(childPath, parentPath) {
|
|
53
|
+
const parent = path.resolve(parentPath) + path.sep;
|
|
54
|
+
const child = path.resolve(childPath) + path.sep;
|
|
55
|
+
return child.startsWith(parent);
|
|
56
|
+
}
|
|
57
|
+
function getVenvPythonPath(workflowPath) {
|
|
58
|
+
return path.join(workflowPath, ".venv", "bin", "python");
|
|
59
|
+
}
|
|
60
|
+
function getVenvBinDir(workflowPath) {
|
|
61
|
+
return path.join(workflowPath, ".venv", "bin");
|
|
62
|
+
}
|
|
63
|
+
async function assertPathExists(p) {
|
|
64
|
+
await fsp.access(p);
|
|
65
|
+
}
|
|
66
|
+
async function assertDirectory(p) {
|
|
67
|
+
const stats = await fsp.stat(p);
|
|
68
|
+
if (!stats.isDirectory()) {
|
|
69
|
+
throw new Error(`Expected directory at ${p}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async function assertVenvExists(workflowPath) {
|
|
73
|
+
const venvPath = path.join(workflowPath, ".venv");
|
|
74
|
+
const stats = await fsp.stat(venvPath);
|
|
75
|
+
if (!stats.isDirectory()) {
|
|
76
|
+
throw new Error(`Virtual environment path is not a directory: ${venvPath}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function parseTimeoutSeconds(raw) {
|
|
80
|
+
if (typeof raw !== "number" || !Number.isFinite(raw))
|
|
81
|
+
return null;
|
|
82
|
+
if (raw <= 0)
|
|
83
|
+
return null;
|
|
84
|
+
return Math.min(Math.floor(raw), 24 * 60 * 60);
|
|
85
|
+
}
|
|
86
|
+
function coerceBoolean(value) {
|
|
87
|
+
if (typeof value === "boolean")
|
|
88
|
+
return value;
|
|
89
|
+
if (typeof value !== "string")
|
|
90
|
+
return null;
|
|
91
|
+
const v = value.trim().toLowerCase();
|
|
92
|
+
if (["true", "1", "yes", "y", "on"].includes(v))
|
|
93
|
+
return true;
|
|
94
|
+
if (["false", "0", "no", "n", "off"].includes(v))
|
|
95
|
+
return false;
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
function coerceNumber(value) {
|
|
99
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
100
|
+
return value;
|
|
101
|
+
if (typeof value !== "string")
|
|
102
|
+
return null;
|
|
103
|
+
const trimmed = value.trim();
|
|
104
|
+
if (!trimmed)
|
|
105
|
+
return null;
|
|
106
|
+
const n = Number(trimmed);
|
|
107
|
+
return Number.isFinite(n) ? n : null;
|
|
108
|
+
}
|
|
109
|
+
function validateInputs(providedInputs, schema) {
|
|
110
|
+
const errors = [];
|
|
111
|
+
const processedInputs = {};
|
|
112
|
+
for (const [name, config] of Object.entries(schema)) {
|
|
113
|
+
const value = providedInputs[name];
|
|
114
|
+
if (value === undefined || value === null) {
|
|
115
|
+
if (config.required !== false && config.default === undefined) {
|
|
116
|
+
errors.push(`Missing required input: '${name}'`);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (config.default !== undefined) {
|
|
120
|
+
processedInputs[name] = config.default;
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
let isValid = false;
|
|
125
|
+
let processedValue = null;
|
|
126
|
+
switch (config.type) {
|
|
127
|
+
case "string":
|
|
128
|
+
if (typeof value === "string") {
|
|
129
|
+
processedValue = value;
|
|
130
|
+
isValid = true;
|
|
131
|
+
}
|
|
132
|
+
else if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
133
|
+
processedValue = String(value);
|
|
134
|
+
isValid = true;
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
case "number":
|
|
138
|
+
{
|
|
139
|
+
const coerced = coerceNumber(value);
|
|
140
|
+
if (coerced !== null) {
|
|
141
|
+
processedValue = coerced;
|
|
142
|
+
isValid = true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
case "boolean":
|
|
147
|
+
{
|
|
148
|
+
const coerced = coerceBoolean(value);
|
|
149
|
+
if (coerced !== null) {
|
|
150
|
+
processedValue = coerced;
|
|
151
|
+
isValid = true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
default:
|
|
156
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
157
|
+
processedValue = value;
|
|
158
|
+
isValid = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (!isValid) {
|
|
162
|
+
errors.push(`Invalid type for '${name}': expected ${config.type}, got ${typeof value}`);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
processedInputs[name] = processedValue;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
for (const name of Object.keys(providedInputs)) {
|
|
169
|
+
if (!(name in schema)) {
|
|
170
|
+
errors.push(`Unexpected input: '${name}' is not defined in workflow.json`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
valid: errors.length === 0,
|
|
175
|
+
errors,
|
|
176
|
+
processedInputs
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function inputsToArgs(inputs) {
|
|
180
|
+
const args = [];
|
|
181
|
+
for (const [name, value] of Object.entries(inputs)) {
|
|
182
|
+
const argName = `--${name}`;
|
|
183
|
+
if (typeof value === "boolean") {
|
|
184
|
+
if (value) {
|
|
185
|
+
args.push(argName);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
args.push(argName, String(value));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return args;
|
|
193
|
+
}
|
|
194
|
+
async function requestWorkflowPermission(opencodeSessionId, messageId, callId) {
|
|
195
|
+
const permission = await client_1.default.tool_execution_permissions.create({
|
|
196
|
+
data: {
|
|
197
|
+
opencode_session_id: opencodeSessionId,
|
|
198
|
+
message_id: messageId,
|
|
199
|
+
call_id: callId,
|
|
200
|
+
status: "pending",
|
|
201
|
+
created_at: BigInt(Date.now())
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
const maxAttempts = 300;
|
|
205
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
206
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
207
|
+
const latest = await client_1.default.tool_execution_permissions.findUnique({
|
|
208
|
+
where: { id: permission.id }
|
|
209
|
+
});
|
|
210
|
+
if (!latest) {
|
|
211
|
+
throw new Error("Permission request not found");
|
|
212
|
+
}
|
|
213
|
+
if (latest.status === "approved") {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (latest.status === "rejected") {
|
|
217
|
+
throw new Error("User denied permission to run this workflow.");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
await client_1.default.tool_execution_permissions.update({
|
|
221
|
+
where: { id: permission.id },
|
|
222
|
+
data: {
|
|
223
|
+
status: "rejected",
|
|
224
|
+
responded_at: BigInt(Date.now())
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
throw new Error("Permission request timed out");
|
|
228
|
+
}
|
|
229
|
+
function runWithTimeout(workflowPath, args, timeoutSeconds, outputFilePath, shouldAbort) {
|
|
230
|
+
return new Promise((resolve) => {
|
|
231
|
+
const venvPython = getVenvPythonPath(workflowPath);
|
|
232
|
+
const runScript = path.join(workflowPath, "run.py");
|
|
233
|
+
const venvBinDir = getVenvBinDir(workflowPath);
|
|
234
|
+
let output = "";
|
|
235
|
+
let outputTruncated = false;
|
|
236
|
+
let finished = false;
|
|
237
|
+
const outputFileStream = (0, fs_1.createWriteStream)(outputFilePath, { flags: "a", encoding: "utf-8" });
|
|
238
|
+
const appendOutput = (text) => {
|
|
239
|
+
if (finished || outputTruncated)
|
|
240
|
+
return;
|
|
241
|
+
const remaining = MAX_OUTPUT_CHARS - output.length;
|
|
242
|
+
if (remaining <= 0) {
|
|
243
|
+
outputTruncated = true;
|
|
244
|
+
output += `\n[WARN] Output truncated after ${MAX_OUTPUT_CHARS} characters\n`;
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (text.length <= remaining) {
|
|
248
|
+
output += text;
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
output += text.slice(0, remaining);
|
|
252
|
+
outputTruncated = true;
|
|
253
|
+
output += `\n[WARN] Output truncated after ${MAX_OUTPUT_CHARS} characters\n`;
|
|
254
|
+
};
|
|
255
|
+
const child = (0, child_process_1.spawn)(venvPython, ["-u", runScript, ...args], {
|
|
256
|
+
cwd: workflowPath,
|
|
257
|
+
env: {
|
|
258
|
+
...process.env,
|
|
259
|
+
PYTHONUNBUFFERED: "1",
|
|
260
|
+
VIRTUAL_ENV: path.join(workflowPath, ".venv"),
|
|
261
|
+
PATH: [venvBinDir, process.env.PATH ?? ""].filter(Boolean).join(path.delimiter),
|
|
262
|
+
},
|
|
263
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
264
|
+
detached: true,
|
|
265
|
+
});
|
|
266
|
+
const pid = child.pid;
|
|
267
|
+
const cleanup = (signal = "SIGTERM") => {
|
|
268
|
+
if (!pid)
|
|
269
|
+
return;
|
|
270
|
+
try {
|
|
271
|
+
process.kill(-pid, signal);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// ignore
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
const finalize = (status) => {
|
|
278
|
+
if (finished)
|
|
279
|
+
return;
|
|
280
|
+
finished = true;
|
|
281
|
+
if (abortPollId) {
|
|
282
|
+
clearInterval(abortPollId);
|
|
283
|
+
abortPollId = null;
|
|
284
|
+
}
|
|
285
|
+
outputFileStream.end();
|
|
286
|
+
resolve({ status, output });
|
|
287
|
+
};
|
|
288
|
+
child.stdout?.on("data", (data) => {
|
|
289
|
+
const text = data.toString();
|
|
290
|
+
appendOutput(text);
|
|
291
|
+
outputFileStream.write(text);
|
|
292
|
+
});
|
|
293
|
+
child.stderr?.on("data", (data) => {
|
|
294
|
+
const text = data.toString();
|
|
295
|
+
appendOutput(text);
|
|
296
|
+
outputFileStream.write(text);
|
|
297
|
+
});
|
|
298
|
+
let abortPollInFlight = false;
|
|
299
|
+
let abortPollId = null;
|
|
300
|
+
abortPollId = setInterval(async () => {
|
|
301
|
+
if (finished || abortPollInFlight)
|
|
302
|
+
return;
|
|
303
|
+
abortPollInFlight = true;
|
|
304
|
+
let abortRequested = false;
|
|
305
|
+
try {
|
|
306
|
+
abortRequested = await shouldAbort();
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
abortRequested = false;
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
abortPollInFlight = false;
|
|
313
|
+
}
|
|
314
|
+
if (!abortRequested || finished)
|
|
315
|
+
return;
|
|
316
|
+
cleanup("SIGTERM");
|
|
317
|
+
setTimeout(() => cleanup("SIGKILL"), 1000);
|
|
318
|
+
const message = "\n[ERROR] Workflow execution was aborted\n";
|
|
319
|
+
appendOutput(message);
|
|
320
|
+
outputFileStream.write(message);
|
|
321
|
+
finalize("aborted");
|
|
322
|
+
}, 500);
|
|
323
|
+
const timeoutId = setTimeout(() => {
|
|
324
|
+
cleanup("SIGTERM");
|
|
325
|
+
setTimeout(() => cleanup("SIGKILL"), 1000);
|
|
326
|
+
const timeoutMessage = `\n[ERROR] Workflow execution timed out after ${timeoutSeconds} seconds\n`;
|
|
327
|
+
appendOutput(timeoutMessage);
|
|
328
|
+
outputFileStream.write(timeoutMessage);
|
|
329
|
+
finalize("timeout");
|
|
330
|
+
}, timeoutSeconds * 1000);
|
|
331
|
+
child.on("close", (code) => {
|
|
332
|
+
clearTimeout(timeoutId);
|
|
333
|
+
if (code === 0) {
|
|
334
|
+
finalize("success");
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (code !== null) {
|
|
338
|
+
appendOutput(`\n[ERROR] Process exited with code ${code}\n`);
|
|
339
|
+
finalize("error");
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
finalize("error");
|
|
343
|
+
});
|
|
344
|
+
child.on("error", (err) => {
|
|
345
|
+
clearTimeout(timeoutId);
|
|
346
|
+
appendOutput(`\n[ERROR] Failed to start process: ${err.message}\n`);
|
|
347
|
+
outputFileStream.write(`\n[ERROR] Failed to start process: ${err.message}\n`);
|
|
348
|
+
finalize("error");
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
async function startWorkflowRunViaBackend(options) {
|
|
353
|
+
if (!SLUG_REGEX.test(options.slug)) {
|
|
354
|
+
throw new Error("Invalid workflow slug. Expected lowercase letters/numbers with optional hyphens.");
|
|
355
|
+
}
|
|
356
|
+
const workflowsRoot = path.join(options.workspaceDir, "workflows");
|
|
357
|
+
const workflowPath = path.join(workflowsRoot, options.slug);
|
|
358
|
+
if (!isPathInside(workflowPath, workflowsRoot)) {
|
|
359
|
+
throw new Error("Invalid workflow path (must be inside workflows/).");
|
|
360
|
+
}
|
|
361
|
+
await assertDirectory(workflowPath);
|
|
362
|
+
await assertPathExists(path.join(workflowPath, "run.py"));
|
|
363
|
+
await assertVenvExists(workflowPath);
|
|
364
|
+
await assertPathExists(getVenvPythonPath(workflowPath));
|
|
365
|
+
if (options.requirePermissionPrompt) {
|
|
366
|
+
await requestWorkflowPermission(options.sessionId, options.messageId, options.callId);
|
|
367
|
+
}
|
|
368
|
+
const workflowJson = JSON.parse(await fsp.readFile(path.join(workflowPath, "workflow.json"), "utf-8"));
|
|
369
|
+
const inputSchema = workflowJson.inputs || {};
|
|
370
|
+
const validation = validateInputs(options.inputs, inputSchema);
|
|
371
|
+
if (!validation.valid) {
|
|
372
|
+
throw new Error(`Input validation failed: ${JSON.stringify(validation.errors)}`);
|
|
373
|
+
}
|
|
374
|
+
const timeoutSeconds = parseTimeoutSeconds(workflowJson.runtime?.timeout_seconds);
|
|
375
|
+
if (!timeoutSeconds) {
|
|
376
|
+
throw new Error(`Could not read runtime.timeout_seconds from workflow.json for '${options.slug}'`);
|
|
377
|
+
}
|
|
378
|
+
const cmdArgs = inputsToArgs(validation.processedInputs);
|
|
379
|
+
const createdRun = await client_1.default.workflow_runs.create({
|
|
380
|
+
data: {
|
|
381
|
+
workflow_slug: options.slug,
|
|
382
|
+
ran_by_user_id: options.authUserId,
|
|
383
|
+
status: "running",
|
|
384
|
+
started_at: BigInt(Date.now()),
|
|
385
|
+
args: JSON.stringify(options.inputs),
|
|
386
|
+
session_id: options.sessionId,
|
|
387
|
+
message_id: options.messageId,
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
const workflowRunsDir = path.join(options.workspaceDir, "workflow-runs");
|
|
391
|
+
await fsp.mkdir(workflowRunsDir, { recursive: true });
|
|
392
|
+
const outputFilePath = path.join(workflowRunsDir, `${sanitizeFilenamePart(options.sessionId)}-${sanitizeFilenamePart(options.messageId)}.txt`);
|
|
393
|
+
await fsp.writeFile(outputFilePath, "", "utf-8");
|
|
394
|
+
const completion = (async () => {
|
|
395
|
+
const runResult = await runWithTimeout(workflowPath, cmdArgs, timeoutSeconds, outputFilePath, async () => {
|
|
396
|
+
return await (0, workflow_interruption_1.isWorkflowSessionInterrupted)(options.sessionId, options.workspaceDir);
|
|
397
|
+
});
|
|
398
|
+
const finalStatus = runResult.status === "success" ? "success" : "failed";
|
|
399
|
+
await client_1.default.workflow_runs.update({
|
|
400
|
+
where: { id: createdRun.id },
|
|
401
|
+
data: {
|
|
402
|
+
status: finalStatus,
|
|
403
|
+
completed_at: BigInt(Date.now()),
|
|
404
|
+
error_message: runResult.status === "success" ? null : runResult.output.slice(-1000),
|
|
405
|
+
output: runResult.output,
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
return {
|
|
409
|
+
status: runResult.status,
|
|
410
|
+
output: runResult.output
|
|
411
|
+
};
|
|
412
|
+
})();
|
|
413
|
+
return { runId: createdRun.id, timeoutSeconds, completion };
|
|
414
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Helper functions for workflow.json operations.
|
|
4
|
+
*/
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getWorkflowPath = getWorkflowPath;
|
|
10
|
+
exports.deleteWorkflow = deleteWorkflow;
|
|
11
|
+
exports.readWorkflowManifestAndEnsurePermissions = readWorkflowManifestAndEnsurePermissions;
|
|
12
|
+
exports.setWorkflowCreator = setWorkflowCreator;
|
|
13
|
+
exports.listWorkflowSlugs = listWorkflowSlugs;
|
|
14
|
+
const fs_1 = __importDefault(require("fs"));
|
|
15
|
+
const path_1 = __importDefault(require("path"));
|
|
16
|
+
const client_1 = __importDefault(require("../prisma/client"));
|
|
17
|
+
const assert_1 = require("./assert");
|
|
18
|
+
const workflow_permissions_1 = require("./workflow-permissions");
|
|
19
|
+
const workspace_sync_1 = require("./workspace-sync");
|
|
20
|
+
/** Get the absolute path to the workspace directory */
|
|
21
|
+
function getWorkspacePath() {
|
|
22
|
+
return (0, workspace_sync_1.getWorkspaceDirFromEnv)();
|
|
23
|
+
}
|
|
24
|
+
/** Get the path to a workflow directory */
|
|
25
|
+
function getWorkflowPath(slug) {
|
|
26
|
+
return path_1.default.join(getWorkspacePath(), "workflows", slug);
|
|
27
|
+
}
|
|
28
|
+
/** Get the path to a workflow's manifest file */
|
|
29
|
+
function getWorkflowManifestPath(slug) {
|
|
30
|
+
return path_1.default.join(getWorkflowPath(slug), "workflow.json");
|
|
31
|
+
}
|
|
32
|
+
/** Delete a workflow directory and all of its contents */
|
|
33
|
+
function deleteWorkflowDirectory(slug) {
|
|
34
|
+
const workflowPath = getWorkflowPath(slug);
|
|
35
|
+
if (!fs_1.default.existsSync(workflowPath)) {
|
|
36
|
+
throw {
|
|
37
|
+
status: 404,
|
|
38
|
+
message: `Workflow not found for slug: ${slug}`
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
fs_1.default.rmSync(workflowPath, { recursive: true, force: false });
|
|
42
|
+
}
|
|
43
|
+
async function deleteWorkflow(slug) {
|
|
44
|
+
await client_1.default.workflow_runs.deleteMany({
|
|
45
|
+
where: { workflow_slug: slug }
|
|
46
|
+
});
|
|
47
|
+
await client_1.default.resource_metadata.deleteMany({
|
|
48
|
+
where: {
|
|
49
|
+
resource_kind: "workflow",
|
|
50
|
+
resource_slug: slug
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
await client_1.default.resource_permissions.deleteMany({
|
|
54
|
+
where: {
|
|
55
|
+
resource_kind: "workflow",
|
|
56
|
+
resource_slug: slug
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
if (fs_1.default.existsSync(getWorkflowPath(slug))) {
|
|
60
|
+
deleteWorkflowDirectory(slug);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Read a workflow's manifest */
|
|
64
|
+
function readWorkflowManifest(slug) {
|
|
65
|
+
const manifestPath = getWorkflowManifestPath(slug);
|
|
66
|
+
if (!fs_1.default.existsSync(manifestPath)) {
|
|
67
|
+
throw {
|
|
68
|
+
status: 404,
|
|
69
|
+
message: `Workflow manifest not found for slug: ${slug}`
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const content = fs_1.default.readFileSync(manifestPath, "utf-8");
|
|
73
|
+
return JSON.parse(content);
|
|
74
|
+
}
|
|
75
|
+
async function readWorkflowManifestAndEnsurePermissions(slug) {
|
|
76
|
+
const manifest = readWorkflowManifest(slug);
|
|
77
|
+
const metadata = await getOrCreateWorkflowMetadataAndEnsurePermission(slug);
|
|
78
|
+
return { manifest, metadata };
|
|
79
|
+
}
|
|
80
|
+
/** Set workflow creator in database metadata */
|
|
81
|
+
async function setWorkflowCreator(slug, userId) {
|
|
82
|
+
const { metadata: existing } = await readWorkflowManifestAndEnsurePermissions(slug);
|
|
83
|
+
if (existing.created_by_user_id) {
|
|
84
|
+
(0, assert_1.assertCondition)(existing.created_by_user_id === userId, "Workflow creator mismatch");
|
|
85
|
+
return existing;
|
|
86
|
+
}
|
|
87
|
+
const now = BigInt(Date.now());
|
|
88
|
+
const row = await client_1.default.resource_metadata.update({
|
|
89
|
+
where: {
|
|
90
|
+
resource_kind_resource_slug: {
|
|
91
|
+
resource_kind: "workflow",
|
|
92
|
+
resource_slug: slug
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
data: {
|
|
96
|
+
created_by_user_id: userId,
|
|
97
|
+
updated_at: now,
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
workflow_slug: row.resource_slug,
|
|
102
|
+
created_by_user_id: row.created_by_user_id,
|
|
103
|
+
approved_by_user_id: row.approved_by_user_id,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** List all workflow slugs */
|
|
107
|
+
function listWorkflowSlugs() {
|
|
108
|
+
const workflowsDir = path_1.default.join(getWorkspacePath(), "workflows");
|
|
109
|
+
if (!fs_1.default.existsSync(workflowsDir)) {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
const entries = fs_1.default.readdirSync(workflowsDir, { withFileTypes: true });
|
|
113
|
+
const slugs = [];
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
if (!entry.isDirectory())
|
|
116
|
+
continue;
|
|
117
|
+
const manifestPath = path_1.default.join(workflowsDir, entry.name, "workflow.json");
|
|
118
|
+
if (fs_1.default.existsSync(manifestPath)) {
|
|
119
|
+
slugs.push(entry.name);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return slugs;
|
|
123
|
+
}
|
|
124
|
+
async function getOrCreateWorkflowMetadataAndEnsurePermission(slug) {
|
|
125
|
+
const existing = await client_1.default.resource_metadata.findUnique({
|
|
126
|
+
where: {
|
|
127
|
+
resource_kind_resource_slug: {
|
|
128
|
+
resource_kind: "workflow",
|
|
129
|
+
resource_slug: slug
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
if (existing) {
|
|
134
|
+
const metadata = {
|
|
135
|
+
workflow_slug: existing.resource_slug,
|
|
136
|
+
created_by_user_id: existing.created_by_user_id,
|
|
137
|
+
approved_by_user_id: existing.approved_by_user_id,
|
|
138
|
+
};
|
|
139
|
+
await (0, workflow_permissions_1.ensureWorkflowRunPermissionsForMetadata)(slug, metadata);
|
|
140
|
+
return metadata;
|
|
141
|
+
}
|
|
142
|
+
const now = BigInt(Date.now());
|
|
143
|
+
const row = await client_1.default.resource_metadata.create({
|
|
144
|
+
data: {
|
|
145
|
+
resource_kind: "workflow",
|
|
146
|
+
resource_slug: slug,
|
|
147
|
+
created_at: now,
|
|
148
|
+
updated_at: now,
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
const metadata = {
|
|
152
|
+
workflow_slug: row.resource_slug,
|
|
153
|
+
created_by_user_id: row.created_by_user_id,
|
|
154
|
+
approved_by_user_id: row.approved_by_user_id,
|
|
155
|
+
};
|
|
156
|
+
await (0, workflow_permissions_1.ensureWorkflowRunPermissionsForMetadata)(slug, metadata);
|
|
157
|
+
return metadata;
|
|
158
|
+
}
|