teamcopilot 0.1.16 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -9
- package/dist/chat/index.js +23 -1
- package/dist/frontend/assets/{cssMode-CH26ItO2.js → cssMode-CM1GmZ3H.js} +1 -1
- package/dist/frontend/assets/{freemarker2-CiRHXG8W.js → freemarker2-C8TeljYR.js} +1 -1
- package/dist/frontend/assets/{handlebars-DXV-JQiR.js → handlebars-B2e-Wzyt.js} +1 -1
- package/dist/frontend/assets/{html-DKdYDRJv.js → html-DtBAvTj2.js} +1 -1
- package/dist/frontend/assets/{htmlMode-D466XPJJ.js → htmlMode-Dta08RE6.js} +1 -1
- package/dist/frontend/assets/index-BirlyHV4.css +1 -0
- package/dist/frontend/assets/{index-CvsPLefz.js → index-Dp0jlIX9.js} +201 -201
- package/dist/frontend/assets/{javascript-D5lHN8tF.js → javascript-BYeHq-2v.js} +1 -1
- package/dist/frontend/assets/{jsonMode-C9Wdxaho.js → jsonMode-DkJo6l8K.js} +1 -1
- package/dist/frontend/assets/{liquid-NIH--tpJ.js → liquid-nmEuajdb.js} +1 -1
- package/dist/frontend/assets/{mdx-xwEbqXME.js → mdx-BJybRyf3.js} +1 -1
- package/dist/frontend/assets/{python-BzErW_b3.js → python-DRAABm9s.js} +1 -1
- package/dist/frontend/assets/{razor-B0v-Bw5B.js → razor-7lH4jzk8.js} +1 -1
- package/dist/frontend/assets/{tsMode-B9YN5EEb.js → tsMode-ClcmdG3S.js} +1 -1
- package/dist/frontend/assets/{typescript-DIMXtHre.js → typescript-D9oav8M6.js} +1 -1
- package/dist/frontend/assets/{xml-DQ5HnppJ.js → xml-B0ks0e6Y.js} +1 -1
- package/dist/frontend/assets/{yaml-BQCOKj13.js → yaml-CCDt1oK4.js} +1 -1
- package/dist/frontend/index.html +2 -2
- package/dist/index.js +99 -90
- package/dist/secrets/index.js +74 -0
- package/dist/skills/index.js +43 -1
- package/dist/users/index.js +98 -0
- package/dist/utils/redact.js +52 -5
- package/dist/utils/resource-file-routes.js +2 -4
- package/dist/utils/resource-files.js +10 -2
- package/dist/utils/secret-contract-validation.js +184 -0
- package/dist/utils/secrets.js +127 -0
- package/dist/utils/skill-files.js +7 -0
- package/dist/utils/skill.js +50 -1
- package/dist/utils/workflow-runner.js +19 -4
- package/dist/utils/workflow.js +13 -1
- package/dist/workflows/index.js +10 -1
- package/dist/workspace_files/.opencode/plugins/createSkill.ts +1 -26
- package/dist/workspace_files/.opencode/plugins/createWorkflow.ts +3 -3
- package/dist/workspace_files/.opencode/plugins/findSimilarWorkflow.ts +93 -5
- package/dist/workspace_files/.opencode/plugins/getSkillContent.ts +31 -49
- package/dist/workspace_files/.opencode/plugins/listAvailableSecretKeys.ts +107 -0
- package/dist/workspace_files/.opencode/plugins/runWorkflow.ts +2 -2
- package/dist/workspace_files/.opencode/plugins/secret-proxy.ts +818 -0
- package/dist/workspace_files/AGENTS.md +91 -21
- package/package.json +5 -3
- package/prisma/generated/client/edge.js +24 -3
- package/prisma/generated/client/index-browser.js +21 -0
- package/prisma/generated/client/index.d.ts +3139 -128
- package/prisma/generated/client/index.js +24 -3
- package/prisma/generated/client/package.json +1 -1
- package/prisma/generated/client/schema.prisma +27 -0
- package/prisma/generated/client/wasm.js +24 -3
- package/prisma/migrations/20260402060129_add_secret_management/migration.sql +38 -0
- package/prisma/migrations/20260404052800_remove_global_secret_user_fkeys/migration.sql +20 -0
- package/prisma/schema.prisma +27 -0
- package/dist/frontend/assets/index-B8Ip8I8F.css +0 -1
|
@@ -20,6 +20,7 @@ interface WorkflowMatch {
|
|
|
20
20
|
slug: string
|
|
21
21
|
similarity: number
|
|
22
22
|
summary: string
|
|
23
|
+
arguments: WorkflowArgumentGroups
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
interface WorkflowSummary {
|
|
@@ -27,6 +28,35 @@ interface WorkflowSummary {
|
|
|
27
28
|
intent_summary: string
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
interface WorkflowInput {
|
|
32
|
+
type: "string" | "number" | "boolean"
|
|
33
|
+
required?: boolean
|
|
34
|
+
default?: string | number | boolean
|
|
35
|
+
description?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface WorkflowManifest {
|
|
39
|
+
inputs?: Record<string, WorkflowInput>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface WorkflowDetailsResponse {
|
|
43
|
+
workflow?: {
|
|
44
|
+
manifest?: WorkflowManifest
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface WorkflowArgument {
|
|
49
|
+
name: string
|
|
50
|
+
type: "string" | "number" | "boolean"
|
|
51
|
+
description: string
|
|
52
|
+
default?: string | number | boolean
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface WorkflowArgumentGroups {
|
|
56
|
+
required: WorkflowArgument[]
|
|
57
|
+
optional: WorkflowArgument[]
|
|
58
|
+
}
|
|
59
|
+
|
|
30
60
|
interface SessionLookupResponse {
|
|
31
61
|
error?: unknown
|
|
32
62
|
data?: {
|
|
@@ -57,6 +87,31 @@ async function readErrorMessageFromResponse(
|
|
|
57
87
|
}
|
|
58
88
|
}
|
|
59
89
|
|
|
90
|
+
function formatWorkflowArguments(inputs: Record<string, WorkflowInput>): WorkflowArgumentGroups {
|
|
91
|
+
const required: WorkflowArgument[] = []
|
|
92
|
+
const optional: WorkflowArgument[] = []
|
|
93
|
+
|
|
94
|
+
for (const [name, input] of Object.entries(inputs)) {
|
|
95
|
+
const baseArgument: WorkflowArgument = {
|
|
96
|
+
name,
|
|
97
|
+
type: input.type,
|
|
98
|
+
description: input.description ?? "",
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (input.required) {
|
|
102
|
+
required.push(baseArgument)
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
optional.push({
|
|
107
|
+
...baseArgument,
|
|
108
|
+
...(input.default !== undefined ? { default: input.default } : {}),
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { required, optional }
|
|
113
|
+
}
|
|
114
|
+
|
|
60
115
|
// ============================================================================
|
|
61
116
|
// Embedding Functions
|
|
62
117
|
// ============================================================================
|
|
@@ -121,7 +176,7 @@ export const FindSimilarWorkflowPlugin: Plugin = async ({ client }) => {
|
|
|
121
176
|
tool: {
|
|
122
177
|
findSimilarWorkflow: tool({
|
|
123
178
|
description:
|
|
124
|
-
"Query for existing workflows before creating new ones or for searching for a workflow to run. Returns up to N candidate workflows with paths and
|
|
179
|
+
"Query for existing workflows before creating new ones or for searching for a workflow to run. Returns up to N candidate workflows with paths, summaries, and argument information based on semantic similarity to the provided description. Use this to avoid duplicate work and find workflows that can be reused or adapted.",
|
|
125
180
|
args: {
|
|
126
181
|
description: tool.schema
|
|
127
182
|
.string()
|
|
@@ -173,7 +228,12 @@ export const FindSimilarWorkflowPlugin: Plugin = async ({ client }) => {
|
|
|
173
228
|
// Get embedding for the query description
|
|
174
229
|
const queryEmbedding = await getEmbedding(description)
|
|
175
230
|
|
|
176
|
-
const
|
|
231
|
+
const rankedMatches: Array<{
|
|
232
|
+
path: string
|
|
233
|
+
slug: string
|
|
234
|
+
similarity: number
|
|
235
|
+
summary: string
|
|
236
|
+
}> = []
|
|
177
237
|
|
|
178
238
|
for (const workflow of candidateWorkflows) {
|
|
179
239
|
const summary = workflow.intent_summary
|
|
@@ -184,7 +244,7 @@ export const FindSimilarWorkflowPlugin: Plugin = async ({ client }) => {
|
|
|
184
244
|
// Calculate cosine similarity
|
|
185
245
|
const similarity = cosineSimilarity(queryEmbedding, workflowEmbedding)
|
|
186
246
|
|
|
187
|
-
|
|
247
|
+
rankedMatches.push({
|
|
188
248
|
slug: workflow.slug,
|
|
189
249
|
path: `workflows/${workflow.slug}`,
|
|
190
250
|
similarity: Math.round(similarity * 100) / 100,
|
|
@@ -193,8 +253,36 @@ export const FindSimilarWorkflowPlugin: Plugin = async ({ client }) => {
|
|
|
193
253
|
}
|
|
194
254
|
|
|
195
255
|
// Sort by similarity descending and take top N
|
|
196
|
-
|
|
197
|
-
const
|
|
256
|
+
rankedMatches.sort((a, b) => b.similarity - a.similarity)
|
|
257
|
+
const topRankedMatches = rankedMatches.slice(0, limit)
|
|
258
|
+
const topMatches: WorkflowMatch[] = []
|
|
259
|
+
|
|
260
|
+
for (const match of topRankedMatches) {
|
|
261
|
+
const detailsResponse = await fetch(
|
|
262
|
+
`${getApiBaseUrl()}/api/workflows/${encodeURIComponent(match.slug)}`,
|
|
263
|
+
{
|
|
264
|
+
headers: {
|
|
265
|
+
Authorization: `Bearer ${authSessionID}`,
|
|
266
|
+
},
|
|
267
|
+
}
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
if (!detailsResponse.ok) {
|
|
271
|
+
const errorMessage = await readErrorMessageFromResponse(
|
|
272
|
+
detailsResponse,
|
|
273
|
+
`Failed to fetch workflow details for '${match.slug}' (HTTP ${detailsResponse.status})`
|
|
274
|
+
)
|
|
275
|
+
throw new Error(errorMessage)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const detailsPayload = (await detailsResponse.json()) as WorkflowDetailsResponse
|
|
279
|
+
const inputs = detailsPayload.workflow?.manifest?.inputs ?? {}
|
|
280
|
+
|
|
281
|
+
topMatches.push({
|
|
282
|
+
...match,
|
|
283
|
+
arguments: formatWorkflowArguments(inputs),
|
|
284
|
+
})
|
|
285
|
+
}
|
|
198
286
|
|
|
199
287
|
return JSON.stringify({ matches: topMatches }, null, 2)
|
|
200
288
|
},
|
|
@@ -11,18 +11,6 @@ function getApiBaseUrl(): string {
|
|
|
11
11
|
|
|
12
12
|
const SLUG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
|
|
13
13
|
|
|
14
|
-
interface SkillFileContentResponse {
|
|
15
|
-
path: string
|
|
16
|
-
kind: "text" | "binary"
|
|
17
|
-
content?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface SkillDetailsResponse {
|
|
21
|
-
skill?: {
|
|
22
|
-
is_approved?: boolean
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
14
|
interface SessionLookupResponse {
|
|
27
15
|
error?: unknown
|
|
28
16
|
data?: {
|
|
@@ -31,6 +19,19 @@ interface SessionLookupResponse {
|
|
|
31
19
|
}
|
|
32
20
|
}
|
|
33
21
|
|
|
22
|
+
function readSessionLookupErrorMessage(error: unknown, fallbackMessage: string): string {
|
|
23
|
+
if (typeof error === "string" && error.trim().length > 0) {
|
|
24
|
+
return error
|
|
25
|
+
}
|
|
26
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
27
|
+
const message = (error as { message?: unknown }).message
|
|
28
|
+
if (typeof message === "string" && message.trim().length > 0) {
|
|
29
|
+
return message
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return fallbackMessage
|
|
33
|
+
}
|
|
34
|
+
|
|
34
35
|
async function readErrorMessageFromResponse(
|
|
35
36
|
response: Response,
|
|
36
37
|
fallbackMessage: string
|
|
@@ -64,7 +65,12 @@ export const GetSkillContentPlugin: Plugin = async ({ client }) => {
|
|
|
64
65
|
},
|
|
65
66
|
})) as SessionLookupResponse
|
|
66
67
|
if (response.error) {
|
|
67
|
-
throw new Error(
|
|
68
|
+
throw new Error(
|
|
69
|
+
readSessionLookupErrorMessage(
|
|
70
|
+
response.error,
|
|
71
|
+
`Failed to resolve root session for ${currentSessionID}`
|
|
72
|
+
)
|
|
73
|
+
)
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
const parentID = response.data?.parentID
|
|
@@ -80,7 +86,7 @@ export const GetSkillContentPlugin: Plugin = async ({ client }) => {
|
|
|
80
86
|
tool: {
|
|
81
87
|
getSkillContent: tool({
|
|
82
88
|
description:
|
|
83
|
-
"Get the contents of a skill by slug. Uses backend auth and permission checks; returns SKILL.md content
|
|
89
|
+
"Get the contents of a skill by slug. Uses backend auth and permission checks; returns the original unresolved SKILL.md content from disk when the current user has access and all required secrets are configured.",
|
|
84
90
|
args: {
|
|
85
91
|
slug: tool.schema
|
|
86
92
|
.string()
|
|
@@ -97,35 +103,8 @@ export const GetSkillContentPlugin: Plugin = async ({ client }) => {
|
|
|
97
103
|
)
|
|
98
104
|
}
|
|
99
105
|
|
|
100
|
-
const skillDetailsResponse = await fetch(
|
|
101
|
-
`${getApiBaseUrl()}/api/skills/${encodeURIComponent(slug)}`,
|
|
102
|
-
{
|
|
103
|
-
headers: {
|
|
104
|
-
Authorization: `Bearer ${authSessionID}`,
|
|
105
|
-
},
|
|
106
|
-
}
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
if (!skillDetailsResponse.ok) {
|
|
110
|
-
const errorMessage = await readErrorMessageFromResponse(
|
|
111
|
-
skillDetailsResponse,
|
|
112
|
-
`Failed to fetch skill metadata for ${slug} (HTTP ${skillDetailsResponse.status})`
|
|
113
|
-
)
|
|
114
|
-
throw new Error(errorMessage)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const skillDetailsPayload =
|
|
118
|
-
(await skillDetailsResponse.json()) as SkillDetailsResponse
|
|
119
|
-
const isApproved = skillDetailsPayload.skill?.is_approved === true
|
|
120
|
-
|
|
121
|
-
if (!isApproved) {
|
|
122
|
-
throw new Error(
|
|
123
|
-
`Skill \"${slug}\" is not approved yet. Only approved skills can be read through getSkillContent.`
|
|
124
|
-
)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
106
|
const response = await fetch(
|
|
128
|
-
`${getApiBaseUrl()}/api/skills/${encodeURIComponent(slug)}/
|
|
107
|
+
`${getApiBaseUrl()}/api/skills/${encodeURIComponent(slug)}/runtime-content`,
|
|
129
108
|
{
|
|
130
109
|
headers: {
|
|
131
110
|
Authorization: `Bearer ${authSessionID}`,
|
|
@@ -136,22 +115,25 @@ export const GetSkillContentPlugin: Plugin = async ({ client }) => {
|
|
|
136
115
|
if (!response.ok) {
|
|
137
116
|
const errorMessage = await readErrorMessageFromResponse(
|
|
138
117
|
response,
|
|
139
|
-
`Failed to get skill content for ${slug} (HTTP ${response.status})`
|
|
118
|
+
`Failed to get runtime skill content for ${slug} (HTTP ${response.status})`
|
|
140
119
|
)
|
|
141
120
|
throw new Error(errorMessage)
|
|
142
121
|
}
|
|
143
122
|
|
|
144
|
-
const payload = (await response.json()) as
|
|
145
|
-
|
|
146
|
-
|
|
123
|
+
const payload = (await response.json()) as {
|
|
124
|
+
skill?: {
|
|
125
|
+
slug?: string
|
|
126
|
+
path?: string
|
|
127
|
+
content?: string
|
|
128
|
+
}
|
|
147
129
|
}
|
|
148
130
|
|
|
149
131
|
return JSON.stringify(
|
|
150
132
|
{
|
|
151
133
|
skill: {
|
|
152
|
-
slug,
|
|
153
|
-
path: payload.path,
|
|
154
|
-
content: payload.content ?? "",
|
|
134
|
+
slug: payload.skill?.slug ?? slug,
|
|
135
|
+
path: payload.skill?.path ?? "SKILL.md",
|
|
136
|
+
content: payload.skill?.content ?? "",
|
|
155
137
|
},
|
|
156
138
|
},
|
|
157
139
|
null,
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type Plugin, tool } from "@opencode-ai/plugin"
|
|
2
|
+
|
|
3
|
+
function getApiBaseUrl(): string {
|
|
4
|
+
const port = process.env.TEAMCOPILOT_PORT?.trim()
|
|
5
|
+
if (!port) {
|
|
6
|
+
throw new Error("TEAMCOPILOT_PORT must be set.")
|
|
7
|
+
}
|
|
8
|
+
return `http://localhost:${port}`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface SessionLookupResponse {
|
|
12
|
+
error?: unknown
|
|
13
|
+
data?: {
|
|
14
|
+
id?: string
|
|
15
|
+
parentID?: string
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function readErrorMessageFromResponse(
|
|
20
|
+
response: Response,
|
|
21
|
+
fallbackMessage: string
|
|
22
|
+
): Promise<string> {
|
|
23
|
+
try {
|
|
24
|
+
const text = await response.text()
|
|
25
|
+
if (!text) return fallbackMessage
|
|
26
|
+
try {
|
|
27
|
+
const parsed: unknown = JSON.parse(text)
|
|
28
|
+
if (parsed && typeof parsed === "object" && "message" in parsed) {
|
|
29
|
+
const msg = (parsed as { message?: unknown }).message
|
|
30
|
+
if (typeof msg === "string" && msg.trim().length > 0) return msg
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
// fall back to plain text
|
|
34
|
+
}
|
|
35
|
+
return text.trim().length > 0 ? text : fallbackMessage
|
|
36
|
+
} catch {
|
|
37
|
+
return fallbackMessage
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const ListAvailableSecretKeysPlugin: Plugin = async ({ client }) => {
|
|
42
|
+
async function resolveRootSessionID(sessionID: string): Promise<string> {
|
|
43
|
+
let currentSessionID = sessionID
|
|
44
|
+
|
|
45
|
+
while (true) {
|
|
46
|
+
const response = (await client.session.get({
|
|
47
|
+
path: {
|
|
48
|
+
id: currentSessionID,
|
|
49
|
+
},
|
|
50
|
+
})) as SessionLookupResponse
|
|
51
|
+
if (response.error) {
|
|
52
|
+
throw new Error(`Failed to resolve root session for ${currentSessionID}`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const parentID = response.data?.parentID
|
|
56
|
+
if (!parentID) {
|
|
57
|
+
return currentSessionID
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
currentSessionID = parentID
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
tool: {
|
|
66
|
+
listAvailableSecretKeys: tool({
|
|
67
|
+
description:
|
|
68
|
+
"Get all secret keys available to the current user. Returns the merged key inventory after applying TeamCopilot's precedence rules: user secret first, then global secret. This tool does not return plaintext secret values.",
|
|
69
|
+
args: {},
|
|
70
|
+
async execute(_args, context) {
|
|
71
|
+
const { sessionID } = context
|
|
72
|
+
const authSessionID = await resolveRootSessionID(sessionID)
|
|
73
|
+
|
|
74
|
+
const response = await fetch(`${getApiBaseUrl()}/api/users/me/resolved-secrets`, {
|
|
75
|
+
headers: {
|
|
76
|
+
Authorization: `Bearer ${authSessionID}`,
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
const errorMessage = await readErrorMessageFromResponse(
|
|
82
|
+
response,
|
|
83
|
+
`Failed to get resolved secrets (HTTP ${response.status})`
|
|
84
|
+
)
|
|
85
|
+
throw new Error(errorMessage)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const payload = (await response.json()) as {
|
|
89
|
+
secret_keys?: string[]
|
|
90
|
+
total?: number
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return JSON.stringify(
|
|
94
|
+
{
|
|
95
|
+
secret_keys: Array.isArray(payload.secret_keys) ? payload.secret_keys : [],
|
|
96
|
+
total: typeof payload.total === "number" ? payload.total : 0,
|
|
97
|
+
},
|
|
98
|
+
null,
|
|
99
|
+
2
|
|
100
|
+
)
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export default ListAvailableSecretKeysPlugin
|
|
@@ -99,7 +99,7 @@ export const RunWorkflowPlugin: Plugin = async ({ client }) => {
|
|
|
99
99
|
tool: {
|
|
100
100
|
runWorkflow: tool({
|
|
101
101
|
description:
|
|
102
|
-
"Execute a workflow
|
|
102
|
+
"Execute a workflow by slug, optionally passing runtime arguments in `inputs`. The `inputs` object is the supported way to provide workflow parameters: it is validated against the schema in workflow.json and then forwarded to the workflow's `run.py` as command-line arguments. The tool streams output in real-time and enforces the timeout defined in workflow.json.",
|
|
103
103
|
args: {
|
|
104
104
|
slug: tool.schema
|
|
105
105
|
.string()
|
|
@@ -111,7 +111,7 @@ export const RunWorkflowPlugin: Plugin = async ({ client }) => {
|
|
|
111
111
|
.optional()
|
|
112
112
|
.default({})
|
|
113
113
|
.describe(
|
|
114
|
-
"
|
|
114
|
+
"Runtime workflow arguments. Provide key-value pairs matching the `inputs` schema in workflow.json; these values are validated and passed through to `run.py`."
|
|
115
115
|
),
|
|
116
116
|
},
|
|
117
117
|
async execute(args, context) {
|