@markjaquith/agency 2.19.0 → 2.21.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 +67 -5
- package/cli.ts +15 -0
- package/package.json +1 -1
- package/skills/agency/SKILL.md +4 -1
- package/src/cli-parser.test.ts +61 -1
- package/src/cli-parser.ts +168 -7
- package/src/cli.test.ts +62 -0
- package/src/commands/epic.ts +51 -1
- package/src/commands/phase.ts +94 -1
- package/src/commands/task.ts +108 -1
- package/src/commands/work.test.ts +105 -16
- package/src/commands/work.ts +99 -28
- package/src/services/GraphMutationService.test.ts +336 -0
- package/src/services/GraphMutationService.ts +952 -0
- package/src/services/WorkbaseService.test.ts +19 -0
- package/src/services/WorkbaseService.ts +15 -37
- package/src/test-utils.ts +2 -0
- package/src/workbase/dependency-graph.ts +51 -0
- package/src/workbase/runner-command.test.ts +79 -0
- package/src/workbase/runner-command.ts +118 -0
- package/src/workbase/schemas.test.ts +26 -0
- package/src/workbase/schemas.ts +15 -0
|
@@ -235,6 +235,25 @@ describe("WorkbaseService", () => {
|
|
|
235
235
|
).rejects.toThrow("{worktree}")
|
|
236
236
|
})
|
|
237
237
|
|
|
238
|
+
test("rejects an unknown runner command placeholder", async () => {
|
|
239
|
+
await write(
|
|
240
|
+
root,
|
|
241
|
+
"agency.json",
|
|
242
|
+
JSON.stringify({
|
|
243
|
+
version: 2,
|
|
244
|
+
runners: { custom: { command: ["agent", "{unknown}"] } },
|
|
245
|
+
}),
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
await expect(
|
|
249
|
+
runTestEffect(
|
|
250
|
+
WorkbaseService.pipe(
|
|
251
|
+
Effect.flatMap((service) => service.discover(root)),
|
|
252
|
+
),
|
|
253
|
+
),
|
|
254
|
+
).rejects.toThrow("{unknown}")
|
|
255
|
+
})
|
|
256
|
+
|
|
238
257
|
test("validates a workbase with an epic and multi-phase task", async () => {
|
|
239
258
|
await write(root, "agency.json", '{"version":2}\n')
|
|
240
259
|
await mkdir(join(root, "repos/agency"), { recursive: true })
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
TaskFrontmatter,
|
|
14
14
|
WorkbaseConfig,
|
|
15
15
|
WorkbaseRegistry,
|
|
16
|
-
type Dependency,
|
|
17
16
|
type EpicFrontmatter as EpicData,
|
|
18
17
|
type PhaseFrontmatter as PhaseData,
|
|
19
18
|
type TaskFrontmatter as TaskData,
|
|
@@ -21,6 +20,8 @@ import {
|
|
|
21
20
|
type WorkbaseRegistration,
|
|
22
21
|
} from "../workbase/schemas"
|
|
23
22
|
import { validateWorktreeCreateCommand } from "../workbase/worktree-command"
|
|
23
|
+
import { validateRunners } from "../workbase/runner-command"
|
|
24
|
+
import { findDependencyCycles } from "../workbase/dependency-graph"
|
|
24
25
|
|
|
25
26
|
class WorkbaseNotFoundError extends Data.TaggedError("WorkbaseNotFoundError")<{
|
|
26
27
|
readonly message: string
|
|
@@ -144,40 +145,6 @@ const writeRegistry = (
|
|
|
144
145
|
yield* fs.writeJSON(path, registry)
|
|
145
146
|
})
|
|
146
147
|
|
|
147
|
-
const findCycles = (nodes: readonly Dependency[]): readonly string[] => {
|
|
148
|
-
const dependencies = new Map(
|
|
149
|
-
nodes.map((node) => [node.id, [...(node.dependsOn ?? [])]]),
|
|
150
|
-
)
|
|
151
|
-
const visiting = new Set<string>()
|
|
152
|
-
const visited = new Set<string>()
|
|
153
|
-
const cycles = new Set<string>()
|
|
154
|
-
|
|
155
|
-
const visit = (id: string) => {
|
|
156
|
-
if (visiting.has(id)) {
|
|
157
|
-
cycles.add(id)
|
|
158
|
-
return
|
|
159
|
-
}
|
|
160
|
-
if (visited.has(id)) {
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
visiting.add(id)
|
|
165
|
-
for (const dependency of dependencies.get(id) ?? []) {
|
|
166
|
-
if (dependencies.has(dependency)) {
|
|
167
|
-
visit(dependency)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
visiting.delete(id)
|
|
171
|
-
visited.add(id)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
for (const id of dependencies.keys()) {
|
|
175
|
-
visit(id)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return [...cycles].sort()
|
|
179
|
-
}
|
|
180
|
-
|
|
181
148
|
export class WorkbaseService extends Effect.Service<WorkbaseService>()(
|
|
182
149
|
"WorkbaseService",
|
|
183
150
|
{
|
|
@@ -277,6 +244,17 @@ export class WorkbaseService extends Effect.Service<WorkbaseService>()(
|
|
|
277
244
|
})
|
|
278
245
|
}
|
|
279
246
|
}
|
|
247
|
+
try {
|
|
248
|
+
validateRunners(decoded.value.runners)
|
|
249
|
+
} catch (cause) {
|
|
250
|
+
return yield* new WorkbaseConfigError({
|
|
251
|
+
path: configPath,
|
|
252
|
+
message:
|
|
253
|
+
cause instanceof Error
|
|
254
|
+
? cause.message
|
|
255
|
+
: "Invalid runner configuration",
|
|
256
|
+
})
|
|
257
|
+
}
|
|
280
258
|
return current
|
|
281
259
|
}
|
|
282
260
|
}
|
|
@@ -681,7 +659,7 @@ export class WorkbaseService extends Effect.Service<WorkbaseService>()(
|
|
|
681
659
|
}
|
|
682
660
|
}
|
|
683
661
|
}
|
|
684
|
-
for (const cycle of
|
|
662
|
+
for (const cycle of findDependencyCycles(epic.data.tasks)) {
|
|
685
663
|
issue(epic.path, `Task dependency cycle includes '${cycle}'`)
|
|
686
664
|
}
|
|
687
665
|
}
|
|
@@ -727,7 +705,7 @@ export class WorkbaseService extends Effect.Service<WorkbaseService>()(
|
|
|
727
705
|
issue(task.path, `Unlisted phase '${phaseId}'`)
|
|
728
706
|
}
|
|
729
707
|
}
|
|
730
|
-
for (const cycle of
|
|
708
|
+
for (const cycle of findDependencyCycles(task.data.phases)) {
|
|
731
709
|
issue(task.path, `Phase dependency cycle includes '${cycle}'`)
|
|
732
710
|
}
|
|
733
711
|
} else if (actualPhaseIds.length > 0) {
|
package/src/test-utils.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { GraphService } from "./services/GraphService"
|
|
|
18
18
|
import { ClaimService } from "./services/ClaimService"
|
|
19
19
|
import { SyncService } from "./services/SyncService"
|
|
20
20
|
import { ReadinessService } from "./services/ReadinessService"
|
|
21
|
+
import { GraphMutationService } from "./services/GraphMutationService"
|
|
21
22
|
|
|
22
23
|
export const createTempDir = () => mkdtemp(join(tmpdir(), "agency-test-"))
|
|
23
24
|
|
|
@@ -40,6 +41,7 @@ const TestLayer = Layer.mergeAll(
|
|
|
40
41
|
ClaimService.Default,
|
|
41
42
|
SyncService.Default,
|
|
42
43
|
ReadinessService.Default,
|
|
44
|
+
GraphMutationService.Default,
|
|
43
45
|
)
|
|
44
46
|
|
|
45
47
|
export async function runTestEffect<A, E>(
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Dependency } from "./schemas"
|
|
2
|
+
|
|
3
|
+
export const findDependencyCycles = (
|
|
4
|
+
nodes: readonly Dependency[],
|
|
5
|
+
): readonly string[] => {
|
|
6
|
+
const dependencies = new Map(
|
|
7
|
+
nodes.map((node) => [node.id, [...(node.dependsOn ?? [])]]),
|
|
8
|
+
)
|
|
9
|
+
const visiting = new Set<string>()
|
|
10
|
+
const visited = new Set<string>()
|
|
11
|
+
const cycles = new Set<string>()
|
|
12
|
+
|
|
13
|
+
const visit = (id: string) => {
|
|
14
|
+
if (visiting.has(id)) {
|
|
15
|
+
cycles.add(id)
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
if (visited.has(id)) return
|
|
19
|
+
|
|
20
|
+
visiting.add(id)
|
|
21
|
+
for (const dependency of dependencies.get(id) ?? []) {
|
|
22
|
+
if (dependencies.has(dependency)) visit(dependency)
|
|
23
|
+
}
|
|
24
|
+
visiting.delete(id)
|
|
25
|
+
visited.add(id)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
for (const id of dependencies.keys()) visit(id)
|
|
29
|
+
return [...cycles].sort()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const validateDependencies = (
|
|
33
|
+
nodes: readonly Dependency[],
|
|
34
|
+
label: string,
|
|
35
|
+
): string | undefined => {
|
|
36
|
+
const singular = label.endsWith("s") ? label.slice(0, -1) : label
|
|
37
|
+
const ids = new Set(nodes.map((node) => node.id))
|
|
38
|
+
if (ids.size !== nodes.length) return `${label} IDs must be unique`
|
|
39
|
+
for (const node of nodes) {
|
|
40
|
+
for (const dependency of node.dependsOn ?? []) {
|
|
41
|
+
if (dependency === node.id) {
|
|
42
|
+
return `${singular} '${node.id}' cannot depend on itself`
|
|
43
|
+
}
|
|
44
|
+
if (!ids.has(dependency)) {
|
|
45
|
+
return `Unknown ${singular.toLowerCase()} dependency '${dependency}'`
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const cycle = findDependencyCycles(nodes)[0]
|
|
50
|
+
return cycle ? `${singular} dependency cycle includes '${cycle}'` : undefined
|
|
51
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import {
|
|
3
|
+
printableEnvironment,
|
|
4
|
+
resolveRunnerCommand,
|
|
5
|
+
runnerEnvironment,
|
|
6
|
+
validateRunners,
|
|
7
|
+
} from "./runner-command"
|
|
8
|
+
|
|
9
|
+
const variables = {
|
|
10
|
+
prompt: "Read the task.",
|
|
11
|
+
workbase: "/workbase",
|
|
12
|
+
target: "execution-unit:phase/task/build",
|
|
13
|
+
task: "task",
|
|
14
|
+
phase: "build",
|
|
15
|
+
claimant: "orchestrator",
|
|
16
|
+
sessionId: "session-1",
|
|
17
|
+
claimRevision: "revision-1",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("runner commands", () => {
|
|
21
|
+
test("uses deterministic fresh and resume commands for built-in presets", () => {
|
|
22
|
+
expect(
|
|
23
|
+
resolveRunnerCommand("opencode", undefined, variables, false).argv,
|
|
24
|
+
).toEqual(["opencode", "--prompt", "Read the task."])
|
|
25
|
+
expect(
|
|
26
|
+
resolveRunnerCommand("opencode", undefined, variables, true).argv,
|
|
27
|
+
).toEqual(["opencode", "--continue", "--prompt", "Read the task."])
|
|
28
|
+
expect(
|
|
29
|
+
resolveRunnerCommand("claude", undefined, variables, true).argv,
|
|
30
|
+
).toEqual(["claude", "--continue", "Read the task."])
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test("expands configured argv and environment without a shell", () => {
|
|
34
|
+
const resolved = resolveRunnerCommand(
|
|
35
|
+
"custom",
|
|
36
|
+
{
|
|
37
|
+
custom: {
|
|
38
|
+
command: ["agent", "--target={target}", "{prompt}"],
|
|
39
|
+
environment: { CUSTOM_SESSION: "{sessionId}" },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
variables,
|
|
43
|
+
false,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
expect(resolved).toEqual({
|
|
47
|
+
argv: [
|
|
48
|
+
"agent",
|
|
49
|
+
"--target=execution-unit:phase/task/build",
|
|
50
|
+
"Read the task.",
|
|
51
|
+
],
|
|
52
|
+
environment: { CUSTOM_SESSION: "session-1" },
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test("rejects unknown placeholders", () => {
|
|
57
|
+
expect(() =>
|
|
58
|
+
validateRunners({ custom: { command: ["agent", "{unknown}"] } }),
|
|
59
|
+
).toThrow("Unknown runner 'custom' placeholder: {unknown}")
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test("provides normalized Agency environment and filters secret values", () => {
|
|
63
|
+
const environment = {
|
|
64
|
+
...runnerEnvironment("custom", variables),
|
|
65
|
+
VISIBLE: "yes",
|
|
66
|
+
ACCESS_TOKEN: "secret",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
expect(environment).toMatchObject({
|
|
70
|
+
AGENCY_RUNNER: "custom",
|
|
71
|
+
AGENCY_CLAIMANT: "orchestrator",
|
|
72
|
+
AGENCY_TARGET: "execution-unit:phase/task/build",
|
|
73
|
+
AGENCY_TASK_ID: "task",
|
|
74
|
+
AGENCY_PHASE_ID: "build",
|
|
75
|
+
})
|
|
76
|
+
expect(printableEnvironment(environment).VISIBLE).toBe("yes")
|
|
77
|
+
expect(printableEnvironment(environment).ACCESS_TOKEN).toBeUndefined()
|
|
78
|
+
})
|
|
79
|
+
})
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { WorkbaseConfig } from "./schemas"
|
|
2
|
+
|
|
3
|
+
export interface RunnerCommandVariables {
|
|
4
|
+
readonly prompt: string
|
|
5
|
+
readonly workbase: string
|
|
6
|
+
readonly target: string
|
|
7
|
+
readonly task: string
|
|
8
|
+
readonly phase: string
|
|
9
|
+
readonly claimant: string
|
|
10
|
+
readonly sessionId: string
|
|
11
|
+
readonly claimRevision: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface RunnerDefinition {
|
|
15
|
+
readonly command: readonly string[]
|
|
16
|
+
readonly resumeCommand?: readonly string[]
|
|
17
|
+
readonly environment?: Readonly<Record<string, string>>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const PLACEHOLDERS = new Set<keyof RunnerCommandVariables>([
|
|
21
|
+
"prompt",
|
|
22
|
+
"workbase",
|
|
23
|
+
"target",
|
|
24
|
+
"task",
|
|
25
|
+
"phase",
|
|
26
|
+
"claimant",
|
|
27
|
+
"sessionId",
|
|
28
|
+
"claimRevision",
|
|
29
|
+
])
|
|
30
|
+
|
|
31
|
+
const BUILTIN_RUNNERS: Readonly<Record<string, RunnerDefinition>> = {
|
|
32
|
+
opencode: {
|
|
33
|
+
command: ["opencode", "--prompt", "{prompt}"],
|
|
34
|
+
resumeCommand: ["opencode", "--continue", "--prompt", "{prompt}"],
|
|
35
|
+
},
|
|
36
|
+
claude: {
|
|
37
|
+
command: ["claude", "{prompt}"],
|
|
38
|
+
resumeCommand: ["claude", "--continue", "{prompt}"],
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const validateTemplate = (runner: string, value: string) => {
|
|
43
|
+
for (const match of value.matchAll(/\{([^{}]+)\}/g)) {
|
|
44
|
+
const placeholder = match[1]!
|
|
45
|
+
if (!PLACEHOLDERS.has(placeholder as keyof RunnerCommandVariables)) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Unknown runner '${runner}' placeholder: {${placeholder}}`,
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const validateRunners = (runners: WorkbaseConfig["runners"]): void => {
|
|
54
|
+
for (const [name, runner] of Object.entries(runners ?? {})) {
|
|
55
|
+
for (const value of [
|
|
56
|
+
...runner.command,
|
|
57
|
+
...(runner.resumeCommand ?? []),
|
|
58
|
+
...Object.values(runner.environment ?? {}),
|
|
59
|
+
]) {
|
|
60
|
+
validateTemplate(name, value)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const expand = (value: string, variables: RunnerCommandVariables) =>
|
|
66
|
+
value.replaceAll(
|
|
67
|
+
/\{([^{}]+)\}/g,
|
|
68
|
+
(match, placeholder: string) =>
|
|
69
|
+
variables[placeholder as keyof RunnerCommandVariables] ?? match,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
export const resolveRunnerCommand = (
|
|
73
|
+
name: string,
|
|
74
|
+
configured: WorkbaseConfig["runners"],
|
|
75
|
+
variables: RunnerCommandVariables,
|
|
76
|
+
resume: boolean,
|
|
77
|
+
) => {
|
|
78
|
+
validateRunners(configured)
|
|
79
|
+
const definition = configured?.[name] ?? BUILTIN_RUNNERS[name]
|
|
80
|
+
if (!definition) throw new Error(`Unknown runner: ${name}`)
|
|
81
|
+
const template =
|
|
82
|
+
resume && definition.resumeCommand
|
|
83
|
+
? definition.resumeCommand
|
|
84
|
+
: definition.command
|
|
85
|
+
const argv = template.map((argument) => expand(argument, variables))
|
|
86
|
+
const environment = Object.fromEntries(
|
|
87
|
+
Object.entries(definition.environment ?? {}).map(([key, value]) => [
|
|
88
|
+
key,
|
|
89
|
+
expand(value, variables),
|
|
90
|
+
]),
|
|
91
|
+
)
|
|
92
|
+
return { argv, environment }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const runnerEnvironment = (
|
|
96
|
+
runner: string,
|
|
97
|
+
variables: RunnerCommandVariables,
|
|
98
|
+
): Record<string, string> => ({
|
|
99
|
+
AGENCY_RUNNER: runner,
|
|
100
|
+
AGENCY_CLAIMANT: variables.claimant,
|
|
101
|
+
AGENCY_SESSION_ID: variables.sessionId,
|
|
102
|
+
AGENCY_CLAIM_REVISION: variables.claimRevision,
|
|
103
|
+
AGENCY_WORKBASE: variables.workbase,
|
|
104
|
+
AGENCY_TARGET: variables.target,
|
|
105
|
+
AGENCY_TASK_ID: variables.task,
|
|
106
|
+
AGENCY_PHASE_ID: variables.phase,
|
|
107
|
+
AGENCY_PROMPT: variables.prompt,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const SECRET_NAME =
|
|
111
|
+
/(secret|token|password|credential|api[_-]?key|private[_-]?key)/i
|
|
112
|
+
|
|
113
|
+
export const printableEnvironment = (environment: Record<string, string>) =>
|
|
114
|
+
Object.fromEntries(
|
|
115
|
+
Object.entries(environment)
|
|
116
|
+
.filter(([key]) => !SECRET_NAME.test(key))
|
|
117
|
+
.sort(([left], [right]) => left.localeCompare(right)),
|
|
118
|
+
)
|
|
@@ -78,6 +78,32 @@ describe("body-of-work descriptions", () => {
|
|
|
78
78
|
})
|
|
79
79
|
})
|
|
80
80
|
|
|
81
|
+
describe("runner configuration", () => {
|
|
82
|
+
test("accepts named argv commands with resume commands and environment", () => {
|
|
83
|
+
const config = Schema.decodeUnknownSync(WorkbaseConfig)({
|
|
84
|
+
version: 2,
|
|
85
|
+
runners: {
|
|
86
|
+
custom: {
|
|
87
|
+
command: ["agent", "{prompt}"],
|
|
88
|
+
resumeCommand: ["agent", "resume", "{sessionId}"],
|
|
89
|
+
environment: { CUSTOM_TARGET: "{target}" },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
expect(config.runners?.custom?.command).toEqual(["agent", "{prompt}"])
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test("rejects shell strings in place of argv arrays", () => {
|
|
98
|
+
expect(() =>
|
|
99
|
+
Schema.decodeUnknownSync(WorkbaseConfig)({
|
|
100
|
+
version: 2,
|
|
101
|
+
runners: { custom: { command: "agent {prompt}" } },
|
|
102
|
+
}),
|
|
103
|
+
).toThrow()
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
81
107
|
describe("work status", () => {
|
|
82
108
|
const supportedStatuses: Record<WorkStatus, true> = {
|
|
83
109
|
open: true,
|
package/src/workbase/schemas.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Schema } from "@effect/schema"
|
|
2
2
|
|
|
3
3
|
const NonEmptyString = Schema.String.pipe(Schema.minLength(1))
|
|
4
|
+
const EnvironmentName = NonEmptyString.pipe(
|
|
5
|
+
Schema.pattern(/^[A-Za-z_][A-Za-z0-9_]*$/),
|
|
6
|
+
)
|
|
4
7
|
|
|
5
8
|
const Description = Schema.optional(NonEmptyString)
|
|
6
9
|
|
|
@@ -52,6 +55,18 @@ export const WorkbaseConfig = Schema.Struct({
|
|
|
52
55
|
version: Schema.Literal(2),
|
|
53
56
|
chooserCommand: Schema.optional(Schema.NonEmptyArray(NonEmptyString)),
|
|
54
57
|
worktreeCreateCommand: Schema.optional(Schema.NonEmptyArray(NonEmptyString)),
|
|
58
|
+
runners: Schema.optional(
|
|
59
|
+
Schema.Record({
|
|
60
|
+
key: EntityId,
|
|
61
|
+
value: Schema.Struct({
|
|
62
|
+
command: Schema.NonEmptyArray(NonEmptyString),
|
|
63
|
+
resumeCommand: Schema.optional(Schema.NonEmptyArray(NonEmptyString)),
|
|
64
|
+
environment: Schema.optional(
|
|
65
|
+
Schema.Record({ key: EnvironmentName, value: Schema.String }),
|
|
66
|
+
),
|
|
67
|
+
}),
|
|
68
|
+
}),
|
|
69
|
+
),
|
|
55
70
|
})
|
|
56
71
|
|
|
57
72
|
export const LegacyWorkbaseRegistry = Schema.Struct({
|